7
mirror of https://gitlab.com/kicad/code/kicad.git synced 2025-04-19 00:21:36 +00:00

Rework Copper Sliver check

Adds QA checks to copper sliver tests.  Adds the following checks:
- Dot product between two arms (quickly avoids checks for >90°)
- Checks the sliver is convex (area test)
- Eliminates minor slivers with angles that are approximately 0 and ones
  with the opposite side width beneath a configurable level
- Updates Clipper2 to fix a couple of jagged edges on inflate
- Adds simplify during zone fill inflation to limit jaggies

Fixes https://gitlab.com/kicad/code/kicad/issues/14549
This commit is contained in:
Seth Hillbrand 2023-04-25 10:26:03 -07:00
parent dd6e3c0432
commit f7f52d77e4
20 changed files with 93308 additions and 186 deletions

View File

@ -1009,7 +1009,7 @@ public:
* #ROUND_ALL_CORNERS to round regardless of angles
*/
void Inflate( int aAmount, int aCircleSegCount,
CORNER_STRATEGY aCornerStrategy = ROUND_ALL_CORNERS );
CORNER_STRATEGY aCornerStrategy = ROUND_ALL_CORNERS, bool aSimplify = false );
void Deflate( int aAmount, int aCircleSegmentsCount,
CORNER_STRATEGY aCornerStrategy = CHAMFER_ALL_CORNERS )
@ -1402,7 +1402,7 @@ private:
const std::vector<SHAPE_ARC>& aArcBuffer );
void inflate1( int aAmount, int aCircleSegCount, CORNER_STRATEGY aCornerStrategy );
void inflate2( int aAmount, int aCircleSegCount, CORNER_STRATEGY aCornerStrategy );
void inflate2( int aAmount, int aCircleSegCount, CORNER_STRATEGY aCornerStrategy, bool aSimplify = false );
/**
* This is the engine to execute all polygon boolean transforms (AND, OR, ... and polygon

View File

@ -992,7 +992,8 @@ void SHAPE_POLY_SET::inflate1( int aAmount, int aCircleSegCount, CORNER_STRATEGY
}
void SHAPE_POLY_SET::inflate2( int aAmount, int aCircleSegCount, CORNER_STRATEGY aCornerStrategy )
void SHAPE_POLY_SET::inflate2( int aAmount, int aCircleSegCount, CORNER_STRATEGY aCornerStrategy,
bool aSimplify )
{
using namespace Clipper2Lib;
// A static table to avoid repetitive calculations of the coefficient
@ -1071,17 +1072,35 @@ void SHAPE_POLY_SET::inflate2( int aAmount, int aCircleSegCount, CORNER_STRATEGY
c.MiterLimit( miterLimit );
PolyTree64 tree;
c.Execute( aAmount, tree );
if( aSimplify )
{
Paths64 paths;
c.Execute( aAmount, paths );
Clipper2Lib::SimplifyPaths( paths, std::abs( aAmount ) * coeff, false );
Clipper64 c2;
c2.PreserveCollinear = false;
c2.ReverseSolution = false;
c2.AddSubject( paths );
c2.Execute(ClipType::Union, FillRule::Positive, tree);
}
else
{
c.Execute( aAmount, tree );
}
importTree( tree, zValues, arcBuffer );
tree.Clear();
}
void SHAPE_POLY_SET::Inflate( int aAmount, int aCircleSegCount, CORNER_STRATEGY aCornerStrategy )
void SHAPE_POLY_SET::Inflate( int aAmount, int aCircleSegCount, CORNER_STRATEGY aCornerStrategy,
bool aSimplify )
{
if( ADVANCED_CFG::GetCfg().m_UseClipper2 )
inflate2( aAmount, aCircleSegCount, aCornerStrategy );
inflate2( aAmount, aCircleSegCount, aCornerStrategy, aSimplify );
else
inflate1( aAmount, aCircleSegCount, aCornerStrategy );
}

View File

@ -84,9 +84,11 @@ bool DRC_TEST_PROVIDER_SLIVER_CHECKER::Run()
if( !reportPhase( _( "Running sliver detection on copper layers..." ) ) )
return false; // DRC cancelled
int widthTolerance = pcbIUScale.mmToIU( ADVANCED_CFG::GetCfg().m_SliverWidthTolerance );
int64_t widthTolerance = pcbIUScale.mmToIU( ADVANCED_CFG::GetCfg().m_SliverWidthTolerance );
int64_t squared_width = widthTolerance * widthTolerance;
double angleTolerance = ADVANCED_CFG::GetCfg().m_SliverAngleTolerance;
int testLength = widthTolerance / ( 2 * sin( DEG2RAD( angleTolerance / 2 ) ) );
double cosangleTol = 2.0 * cos( DEG2RAD( angleTolerance ) );
LSET copperLayerSet = m_drcEngine->GetBoard()->GetEnabledLayers() & LSET::AllCuMask();
LSEQ copperLayers = copperLayerSet.Seq();
int layerCount = copperLayers.size();
@ -130,7 +132,6 @@ bool DRC_TEST_PROVIDER_SLIVER_CHECKER::Run()
if( !zone->GetIsRuleArea() )
{
fill = zone->GetFill( layer )->CloneDropTriangulation();
fill.Unfracture( SHAPE_POLY_SET::PM_FAST );
poly.Append( fill );
// Report progress on board zones only. Everything else is
@ -141,7 +142,7 @@ bool DRC_TEST_PROVIDER_SLIVER_CHECKER::Run()
else
{
item->TransformShapeToPolygon( poly, layer, 0, ARC_LOW_DEF,
ERROR_OUTSIDE );
ERROR_INSIDE );
}
if( m_drcEngine->IsCancelled() )
@ -154,11 +155,7 @@ bool DRC_TEST_PROVIDER_SLIVER_CHECKER::Run()
if( m_drcEngine->IsCancelled() )
return 0;
poly.Simplify( SHAPE_POLY_SET::PM_FAST );
// Sharpen corners
poly.Deflate( widthTolerance / 2, ARC_LOW_DEF,
SHAPE_POLY_SET::ALLOW_ACUTE_CORNERS );
poly.Simplify( SHAPE_POLY_SET::POLYGON_MODE::PM_FAST );
return 1;
};
@ -183,7 +180,6 @@ bool DRC_TEST_PROVIDER_SLIVER_CHECKER::Run()
}
}
for( int ii = 0; ii < layerCount; ++ii )
{
PCB_LAYER_ID layer = copperLayers[ii];
@ -203,31 +199,78 @@ bool DRC_TEST_PROVIDER_SLIVER_CHECKER::Run()
{
const std::vector<VECTOR2I>& pts = poly.Outline( jj ).CPoints();
int ptCount = pts.size();
int offset = 0;
for( int kk = 0; kk < ptCount; ++kk )
{
VECTOR2I pt = pts[ kk ];
VECTOR2I ptPrior = pts[ ( ptCount + kk - 1 ) % ptCount ];
VECTOR2I vPrior = ( ptPrior - pt );
if( std::abs( vPrior.x ) < min_len && std::abs( vPrior.y ) < min_len && ptCount > 5)
auto area = [&]( const VECTOR2I& p, const VECTOR2I& q, const VECTOR2I& r ) -> VECTOR2I::extended_type
{
ptPrior = pts[ ( ptCount + kk - 2 ) % ptCount ];
return static_cast<VECTOR2I::extended_type>( q.y - p.y ) * ( r.x - q.x ) -
static_cast<VECTOR2I::extended_type>( q.x - p.x ) * ( r.y - q.y );
};
auto isLocallyInside = [&]( int aA, int aB ) -> bool
{
int prev = ( ptCount + aA - 1 ) % ptCount;
int next = ( aA + 1 ) % ptCount;
if( area( pts[prev], pts[aA], pts[next] ) < 0 )
return area( pts[aA], pts[aB], pts[next] ) >= 0 && area( pts[aA], pts[prev], pts[aB] ) >= 0;
else
return area( pts[aA], pts[aB], pts[prev] ) < 0 || area( pts[aA], pts[next], pts[aB] ) < 0;
};
if( ptCount <= 5 )
continue;
for( int kk = 0; kk < ptCount; kk += offset )
{
int prior_index = ( ptCount + kk - 1 ) % ptCount;
int next_index = ( kk + 1 ) % ptCount;
VECTOR2I pt = pts[ kk ];
VECTOR2I ptPrior = pts[ prior_index ];
VECTOR2I vPrior = ( ptPrior - pt );
int forward_offset = 1;
offset = 1;
while( std::abs( vPrior.x ) < min_len && std::abs( vPrior.y ) < min_len
&& offset < ptCount )
{
pt = pts[ ( kk + offset++ ) % ptCount ];
vPrior = ( ptPrior - pt );
}
VECTOR2I ptAfter = pts[ ( kk + 1 ) % ptCount ];
if( offset >= ptCount )
break;
VECTOR2I ptAfter = pts[ next_index ];
VECTOR2I vAfter = ( ptAfter - pt );
if( std::abs( vAfter.x ) < min_len && std::abs( vAfter.y ) < min_len && ptCount > 5 )
while( std::abs( vAfter.x ) < min_len && std::abs( vAfter.y ) < min_len
&& forward_offset < ptCount )
{
ptAfter = pts[ ( kk + 2 ) % ptCount ];
next_index = ( kk + forward_offset++ ) % ptCount;
ptAfter = pts[ next_index ];
vAfter = ( ptAfter - pt );
}
VECTOR2I vIncluded = vPrior.Resize( testLength ) - vAfter.Resize( testLength );
if( offset >= ptCount )
break;
if( vIncluded.SquaredEuclideanNorm() < SEG::Square( widthTolerance ) )
// Negative dot product means that the angle is > 90°
if( vPrior.Dot( vAfter ) <= 0 )
continue;
if( !isLocallyInside( prior_index, next_index ) )
continue;
VECTOR2I vIncluded = ptAfter - ptPrior;
double arm1 = vPrior.SquaredEuclideanNorm();
double arm2 = vAfter.SquaredEuclideanNorm();
double opp = vIncluded.SquaredEuclideanNorm();
double cos_ang = std::abs( ( opp - arm1 - arm2 ) / ( std::sqrt( arm1 ) * std::sqrt( arm2 ) ) );
if( cos_ang > cosangleTol && 2.0 - cos_ang > std::numeric_limits<float>::epsilon() && opp > squared_width )
{
std::shared_ptr<DRC_ITEM> drce = DRC_ITEM::Create( DRCE_COPPER_SLIVER );
drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + layerDesc( layer ) );

View File

@ -1551,7 +1551,7 @@ bool ZONE_FILLER::fillCopperZone( const ZONE* aZone, PCB_LAYER_ID aLayer, PCB_LA
*/
if( half_min_width - epsilon > epsilon )
aFillPolys.Inflate( half_min_width - epsilon, numSegs, cornerStrategy );
aFillPolys.Inflate( half_min_width - epsilon, numSegs, cornerStrategy, true );
DUMP_POLYS_TO_COPPER_LAYER( aFillPolys, In15_Cu, wxT( "after-reinflating" ) );

View File

LOADING design file

View File

LOADING design file

View File

@ -0,0 +1,537 @@
{
"board": {
"3dviewports": [],
"design_settings": {
"defaults": {
"board_outline_line_width": 0.09999999999999999,
"copper_line_width": 0.19999999999999998,
"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.049999999999999996,
"dimension_precision": 2,
"dimension_units": 2,
"dimensions": {
"arrow_length": 1270000,
"extension_offset": 500000,
"keep_text_aligned": true,
"suppress_zeroes": true,
"text_position": 1,
"units_format": 1
},
"fab_line_width": 0.09999999999999999,
"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.09999999999999999,
"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.621,
"width": 0.95
},
"silk_line_width": 0.15,
"silk_text_italic": false,
"silk_text_size_h": 1.0,
"silk_text_size_v": 1.0,
"silk_text_thickness": 0.15,
"silk_text_upright": false,
"zones": {
"min_clearance": 0.16
}
},
"diff_pair_dimensions": [
{
"gap": 0.0,
"via_gap": 0.0,
"width": 0.0
},
{
"gap": 0.15,
"via_gap": 0.2,
"width": 0.2
}
],
"drc_exclusions": [],
"meta": {
"version": 2
},
"rule_severities": {
"annular_width": "error",
"clearance": "error",
"connection_width": "error",
"copper_edge_clearance": "error",
"copper_sliver": "error",
"courtyards_overlap": "warning",
"diff_pair_gap_out_of_range": "error",
"diff_pair_uncoupled_length_too_long": "error",
"drill_out_of_range": "error",
"duplicate_footprints": "warning",
"extra_footprint": "warning",
"footprint": "error",
"footprint_type_mismatch": "ignore",
"hole_clearance": "error",
"hole_near_hole": "error",
"invalid_outline": "error",
"isolated_copper": "warning",
"item_on_disabled_layer": "error",
"items_not_allowed": "error",
"length_out_of_range": "error",
"lib_footprint_issues": "warning",
"lib_footprint_mismatch": "ignore",
"malformed_courtyard": "ignore",
"microvia_drill_out_of_range": "error",
"missing_courtyard": "ignore",
"missing_footprint": "warning",
"net_conflict": "warning",
"npth_inside_courtyard": "ignore",
"padstack": "warning",
"pth_inside_courtyard": "ignore",
"shorting_items": "error",
"silk_edge_clearance": "ignore",
"silk_over_copper": "ignore",
"silk_overlap": "ignore",
"skew_out_of_range": "error",
"solder_mask_bridge": "ignore",
"starved_thermal": "error",
"text_height": "ignore",
"text_thickness": "ignore",
"through_hole_pad_without_hole": "error",
"too_many_vias": "error",
"track_dangling": "error",
"track_width": "error",
"tracks_crossing": "error",
"unconnected_items": "error",
"unresolved_variable": "error",
"via_dangling": "ignore",
"zones_intersect": "error"
},
"rules": {
"allow_blind_buried_vias": false,
"allow_microvias": false,
"max_error": 0.005,
"min_clearance": 0.16,
"min_connection": 0.0,
"min_copper_edge_clearance": 0.39999999999999997,
"min_hole_clearance": 0.254,
"min_hole_to_hole": 0.5,
"min_microvia_diameter": 0.39999999999999997,
"min_microvia_drill": 0.19999999999999998,
"min_resolved_spokes": 1,
"min_silk_clearance": 0.0,
"min_text_height": 0.6,
"min_text_thickness": 0.15,
"min_through_hole_diameter": 0.19999999999999998,
"min_track_width": 0.16,
"min_via_annular_width": 0.16,
"min_via_diameter": 0.39999999999999997,
"solder_mask_clearance": 0.0,
"solder_mask_min_width": 0.0,
"solder_mask_to_copper_clearance": 0.0,
"use_height_for_length_calcs": true
},
"teardrop_options": [
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 5,
"td_on_pad_in_zone": false,
"td_onpadsmd": true,
"td_onroundshapesonly": false,
"td_ontrackend": true,
"td_onviapad": true
}
],
"teardrop_parameters": [
{
"td_curve_segcount": 5,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 0.5,
"td_target_name": "td_round_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_curve_segcount": 5,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 0.5,
"td_target_name": "td_rect_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_curve_segcount": 5,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 0.5,
"td_target_name": "td_track_end",
"td_width_to_size_filter_ratio": 0.9
}
],
"track_widths": [
0.0,
0.2,
0.3,
0.4,
0.5,
0.6,
0.7,
0.8,
0.9,
1.0,
1.2,
1.5,
2.0,
2.5,
3.0,
4.0,
5.0
],
"via_dimensions": [
{
"diameter": 0.0,
"drill": 0.0
},
{
"diameter": 0.6,
"drill": 0.2
},
{
"diameter": 0.9,
"drill": 0.5
}
],
"zones_allow_external_fillets": false
},
"layer_presets": [],
"viewports": []
},
"boards": [],
"cvpcb": {
"equivalence_files": []
},
"erc": {
"erc_exclusions": [],
"meta": {
"version": 0
},
"pin_map": [
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
1,
0,
1,
2
],
[
0,
1,
0,
0,
0,
0,
1,
1,
2,
1,
1,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
2
],
[
1,
1,
1,
1,
1,
0,
1,
1,
1,
1,
1,
2
],
[
0,
0,
0,
1,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
1,
2,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
0,
2,
1,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2
]
],
"rule_severities": {
"bus_definition_conflict": "error",
"bus_entry_needed": "error",
"bus_to_bus_conflict": "error",
"bus_to_net_conflict": "error",
"conflicting_netclasses": "error",
"different_unit_footprint": "error",
"different_unit_net": "error",
"duplicate_reference": "error",
"duplicate_sheet_names": "error",
"endpoint_off_grid": "warning",
"extra_units": "error",
"global_label_dangling": "warning",
"hier_label_mismatch": "error",
"label_dangling": "error",
"lib_symbol_issues": "warning",
"missing_bidi_pin": "warning",
"missing_input_pin": "warning",
"missing_power_pin": "error",
"missing_unit": "warning",
"multiple_net_names": "warning",
"net_not_bus_member": "warning",
"no_connect_connected": "warning",
"no_connect_dangling": "warning",
"pin_not_connected": "error",
"pin_not_driven": "error",
"pin_to_pin": "error",
"power_pin_not_driven": "error",
"similar_labels": "warning",
"simulation_model_issue": "ignore",
"unannotated": "error",
"unit_value_mismatch": "error",
"unresolved_variable": "error",
"wire_dangling": "error"
}
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "W220921.01.kicad_pro",
"version": 1
},
"net_settings": {
"classes": [
{
"bus_width": 12,
"clearance": 0.15,
"diff_pair_gap": 0.15,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.6,
"microvia_drill": 0.2,
"name": "Default",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.2,
"via_diameter": 0.6,
"via_drill": 0.2,
"wire_width": 6
}
],
"meta": {
"version": 3
},
"net_colors": null,
"netclass_assignments": null,
"netclass_patterns": []
},
"pcbnew": {
"last_paths": {
"gencad": "",
"idf": "",
"netlist": "",
"specctra_dsn": "",
"step": "Assembly/Export STEP/W220921.01.step",
"vrml": ""
},
"page_layout_descr_file": "worksheets/A5_WiSER.kicad_wks"
},
"schematic": {
"annotate_start_num": 0,
"drawing": {
"dashed_lines_dash_length_ratio": 12.0,
"dashed_lines_gap_length_ratio": 3.0,
"default_line_thickness": 6.0,
"default_text_size": 40.0,
"field_names": [],
"intersheets_ref_own_page": false,
"intersheets_ref_prefix": "",
"intersheets_ref_short": false,
"intersheets_ref_show": false,
"intersheets_ref_suffix": "",
"junction_size_choice": 3,
"label_size_ratio": 0.375,
"pin_symbol_size": 25.0,
"text_offset_ratio": 0.15
},
"legacy_lib_dir": "",
"legacy_lib_list": [],
"meta": {
"version": 1
},
"net_format_name": "",
"ngspice": {
"fix_include_paths": true,
"fix_passive_vals": false,
"meta": {
"version": 0
},
"model_mode": 0,
"workbook_filename": ""
},
"page_layout_descr_file": "worksheets\\A4_WiSER.kicad_wks",
"plot_directory": "Documentation/",
"spice_adjust_passive_values": false,
"spice_current_sheet_as_root": false,
"spice_external_command": "spice \"%I\"",
"spice_model_current_sheet_as_root": true,
"spice_save_all_currents": false,
"spice_save_all_voltages": false,
"subpart_first_id": 65,
"subpart_id_separator": 0
},
"sheets": [
[
"3648648f-5acc-4713-a8ab-e21fc1644e2e",
""
]
],
"text_variables": {
"ProjectAuthor": "René Široký",
"ProjectDate": "21. 9. 2022",
"ProjectNumber": "W220921.01",
"ProjectTitle": "TMDSEMU110-U Debug Probe SWD Adapter",
"ProjectVariant": "Default"
}
}

View File

LOADING design file

View File

LOADING design file

View File

@ -0,0 +1,591 @@
{
"board": {
"3dviewports": [],
"design_settings": {
"defaults": {
"board_outline_line_width": 0.09999999999999999,
"copper_line_width": 0.19999999999999998,
"copper_text_italic": false,
"copper_text_size_h": 1.0,
"copper_text_size_v": 1.0,
"copper_text_thickness": 0.15,
"copper_text_upright": false,
"courtyard_line_width": 0.049999999999999996,
"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.09999999999999999,
"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.15,
"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": 2.0,
"height": 2.0,
"width": 2.0
},
"silk_line_width": 0.15,
"silk_text_italic": false,
"silk_text_size_h": 1.0,
"silk_text_size_v": 1.0,
"silk_text_thickness": 0.15,
"silk_text_upright": false,
"zones": {
"45_degree_only": false,
"min_clearance": 0.19999999999999998
}
},
"diff_pair_dimensions": [
{
"gap": 0.0,
"via_gap": 0.0,
"width": 0.0
}
],
"drc_exclusions": [],
"meta": {
"version": 2
},
"rule_severities": {
"annular_width": "error",
"clearance": "ignore",
"connection_width": "error",
"copper_edge_clearance": "error",
"copper_sliver": "error",
"courtyards_overlap": "error",
"diff_pair_gap_out_of_range": "error",
"diff_pair_uncoupled_length_too_long": "error",
"drill_out_of_range": "error",
"duplicate_footprints": "error",
"extra_footprint": "error",
"footprint": "error",
"footprint_type_mismatch": "error",
"hole_clearance": "error",
"hole_near_hole": "error",
"invalid_outline": "error",
"isolated_copper": "error",
"item_on_disabled_layer": "error",
"items_not_allowed": "error",
"length_out_of_range": "error",
"lib_footprint_issues": "ignore",
"lib_footprint_mismatch": "ignore",
"malformed_courtyard": "error",
"microvia_drill_out_of_range": "error",
"missing_courtyard": "error",
"missing_footprint": "error",
"net_conflict": "error",
"npth_inside_courtyard": "error",
"padstack": "error",
"pth_inside_courtyard": "error",
"shorting_items": "error",
"silk_edge_clearance": "error",
"silk_over_copper": "error",
"silk_overlap": "error",
"skew_out_of_range": "error",
"solder_mask_bridge": "error",
"starved_thermal": "error",
"text_height": "error",
"text_thickness": "error",
"through_hole_pad_without_hole": "error",
"too_many_vias": "error",
"track_dangling": "error",
"track_width": "error",
"tracks_crossing": "error",
"unconnected_items": "error",
"unresolved_variable": "error",
"via_dangling": "error",
"zones_intersect": "error"
},
"rules": {
"allow_blind_buried_vias": false,
"allow_microvias": false,
"max_error": 0.005,
"min_clearance": 0.19999999999999998,
"min_connection": 0.19999999999999998,
"min_copper_edge_clearance": 0.19999999999999998,
"min_hole_clearance": 0.19999999999999998,
"min_hole_to_hole": 0.19999999999999998,
"min_microvia_diameter": 0.19999999999999998,
"min_microvia_drill": 0.09999999999999999,
"min_resolved_spokes": 2,
"min_silk_clearance": 0.0,
"min_text_height": 0.7999999999999999,
"min_text_thickness": 0.12,
"min_through_hole_diameter": 0.19999999999999998,
"min_track_width": 0.19999999999999998,
"min_via_annular_width": 0.09999999999999999,
"min_via_diameter": 0.39999999999999997,
"solder_mask_clearance": 0.0,
"solder_mask_min_width": 0.0,
"solder_mask_to_copper_clearance": 0.0,
"use_height_for_length_calcs": true
},
"teardrop_options": [
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 5,
"td_on_pad_in_zone": false,
"td_onpadsmd": true,
"td_onroundshapesonly": false,
"td_ontrackend": false,
"td_onviapad": true
}
],
"teardrop_parameters": [
{
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_target_name": "td_round_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_target_name": "td_rect_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_target_name": "td_track_end",
"td_width_to_size_filter_ratio": 0.9
}
],
"track_widths": [
0.0,
0.2,
0.3,
0.5,
0.8,
2.0
],
"via_dimensions": [
{
"diameter": 0.0,
"drill": 0.0
},
{
"diameter": 0.4,
"drill": 0.2
}
],
"zones_allow_external_fillets": true,
"zones_use_no_outline": true
},
"layer_presets": [],
"viewports": []
},
"boards": [],
"cvpcb": {
"equivalence_files": []
},
"erc": {
"erc_exclusions": [],
"meta": {
"version": 0
},
"pin_map": [
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
1,
0,
1,
2
],
[
0,
1,
0,
0,
0,
0,
1,
1,
2,
1,
1,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
2
],
[
1,
1,
1,
1,
1,
0,
1,
1,
1,
1,
1,
2
],
[
0,
0,
0,
1,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
1,
2,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
0,
2,
1,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2
]
],
"rule_severities": {
"bus_definition_conflict": "error",
"bus_entry_needed": "error",
"bus_to_bus_conflict": "error",
"bus_to_net_conflict": "error",
"conflicting_netclasses": "error",
"different_unit_footprint": "error",
"different_unit_net": "error",
"duplicate_reference": "error",
"duplicate_sheet_names": "error",
"endpoint_off_grid": "error",
"extra_units": "error",
"global_label_dangling": "error",
"hier_label_mismatch": "error",
"label_dangling": "error",
"lib_symbol_issues": "error",
"missing_bidi_pin": "error",
"missing_input_pin": "error",
"missing_power_pin": "error",
"missing_unit": "error",
"multiple_net_names": "error",
"net_not_bus_member": "error",
"no_connect_connected": "error",
"no_connect_dangling": "error",
"pin_not_connected": "error",
"pin_not_driven": "error",
"pin_to_pin": "error",
"power_pin_not_driven": "error",
"similar_labels": "error",
"simulation_model_issue": "error",
"unannotated": "error",
"unit_value_mismatch": "error",
"unresolved_variable": "error",
"wire_dangling": "error"
}
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "pulse.kicad_pro",
"version": 1
},
"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)",
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.2,
"via_diameter": 0.4,
"via_drill": 0.2,
"wire_width": 6
}
],
"meta": {
"version": 3
},
"net_colors": null,
"netclass_assignments": null,
"netclass_patterns": []
},
"pcbnew": {
"last_paths": {
"gencad": "",
"idf": "",
"netlist": "",
"specctra_dsn": "",
"step": "",
"vrml": ""
},
"page_layout_descr_file": "${KICAD_USER_TEMPLATE_DIR}/V2_A4_Empty.kicad_wks"
},
"schematic": {
"annotate_start_num": 0,
"drawing": {
"dashed_lines_dash_length_ratio": 12.0,
"dashed_lines_gap_length_ratio": 3.0,
"default_line_thickness": 6.0,
"default_text_size": 50.0,
"field_names": [],
"intersheets_ref_own_page": false,
"intersheets_ref_prefix": "",
"intersheets_ref_short": false,
"intersheets_ref_show": false,
"intersheets_ref_suffix": "",
"junction_size_choice": 3,
"label_size_ratio": 0.375,
"pin_symbol_size": 25.0,
"text_offset_ratio": 0.1
},
"legacy_lib_dir": "",
"legacy_lib_list": [],
"meta": {
"version": 1
},
"net_format_name": "",
"ngspice": {
"fix_include_paths": true,
"fix_passive_vals": false,
"meta": {
"version": 0
},
"model_mode": 0,
"workbook_filename": ""
},
"page_layout_descr_file": "${KICAD_USER_TEMPLATE_DIR}/V2_A4.kicad_wks",
"plot_directory": "",
"spice_adjust_passive_values": false,
"spice_current_sheet_as_root": false,
"spice_external_command": "spice \"%I\"",
"spice_model_current_sheet_as_root": true,
"spice_save_all_currents": false,
"spice_save_all_voltages": false,
"subpart_first_id": 65,
"subpart_id_separator": 0
},
"sheets": [
[
"6c8448b4-b04d-47e1-934e-e40cbe27a7be",
""
],
[
"ecb8eef8-bb26-4217-a3d3-147bd9b06112",
"PWM Channel 1"
],
[
"c0032080-b223-4ef5-af6e-f10445519ca7",
"PWM Channel 2"
],
[
"72fc00ec-a037-43b2-8be3-f6274b0c3d34",
"PWM Channel 3"
],
[
"c5e21047-c711-42c5-833c-f87be56058f4",
"PWM Channel 4"
],
[
"a89ccf9d-6e1e-42ce-a365-b8a588b2a764",
"PWM Channel 5"
],
[
"2e65de88-75bc-4218-bada-fc991c11693d",
"PWM Channel 6"
],
[
"a178fee9-8654-4c6d-afdd-d0f611515c27",
"PWM Channel 7"
],
[
"c39b559a-eba7-416b-9b64-29e457c350f9",
"PWM Channel 8"
],
[
"f374a830-6190-4414-97f7-cd95a96fcb7b",
"PWM Channel 9"
],
[
"f929036c-a21a-4759-9a71-4bb346b616ac",
"PWM Channel 12"
],
[
"11be0dca-9aa7-44a3-a97c-59531fd35a86",
"PWM Channel 11"
],
[
"0c49b5ee-ddea-43ea-ab5a-6c52ceb8d790",
"PWM Channel 10"
],
[
"87daeacc-2e6b-4f3e-a4a8-59a3ffd747c2",
"PWM Channel 13"
],
[
"1b24bac2-35ae-4e4e-8bc5-d88b5d418adf",
"PWM Channel 16"
],
[
"02d7dc07-d88b-46a9-8c27-5f474f94bba5",
"PWM Channel 15"
],
[
"ef3f4e24-65f6-49ab-9666-2948510217b4",
"PWM Channel 14"
],
[
"ba748654-beb9-4861-aa35-c19e08caaf2e",
"Power +24V"
],
[
"c6db2f80-f80f-4104-8ca1-02f0ab85cfea",
"Link Plug"
],
[
"2a03ea79-269e-4abf-833c-000ba0ff13d7",
"Link Socket"
]
],
"text_variables": {
"Order-Number": "JLCJLCJLCJLC"
}
}

View File

LOADING design file

View File

@ -47,6 +47,7 @@ set( QA_PCBNEW_SRCS
drc/test_drc_courtyard_overlap.cpp
drc/test_drc_regressions.cpp
drc/test_drc_copper_conn.cpp
drc/test_drc_copper_sliver.cpp
drc/test_solder_mask_bridging.cpp
plugins/altium/test_altium_rule_transformer.cpp

View File

@ -0,0 +1,106 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2023 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
{
DRC_REGRESSION_TEST_FIXTURE() :
m_settingsManager( true /* headless */ )
{ }
SETTINGS_MANAGER m_settingsManager;
std::unique_ptr<BOARD> m_board;
};
BOOST_FIXTURE_TEST_CASE( DRCCopperSliver, DRC_REGRESSION_TEST_FIXTURE )
{
// Check for minimum copper connection errors
std::vector<std::pair<wxString, int>> tests =
{
{ "sliver", 1 },
{ "issue14449", 0 },
{ "issue14549", 0 },
{ "issue14549_2", 0 },
{ "issue14559", 0 }
};
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();
// Disable DRC tests not useful or not handled in this testcase
for( int ii = DRCE_FIRST; ii <= DRCE_LAST; ++ii )
bds.m_DRCSeverities[ ii ] = SEVERITY::RPT_SEVERITY_IGNORE;
// Ensure that our desired error is fired
bds.m_DRCSeverities[ DRCE_COPPER_SLIVER ] = SEVERITY::RPT_SEVERITY_ERROR;
bds.m_DRCEngine->SetViolationHandler(
[&]( const std::shared_ptr<DRC_ITEM>& aItem, VECTOR2I aPos, int aLayer )
{
if( bds.GetSeverity( aItem->GetErrorCode() ) == SEVERITY::RPT_SEVERITY_ERROR )
violations.push_back( *aItem );
} );
bds.m_DRCEngine->RunTests( EDA_UNITS::MILLIMETRES, 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 copper sliver: %s, passed", test.first ) );
}
else
{
UNITS_PROVIDER unitsProvider( pcbIUScale, EDA_UNITS::INCHES );
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 copper sliver: %s, failed (violations found %d expected %d)",
test.first, (int)violations.size(), test.second ) );
}
}
}

View File

@ -1,6 +1,6 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 22 March 2023 *
* Date : 8 April 2023 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2023 *
* Purpose : Core Clipper Library structures and functions *
@ -50,7 +50,9 @@ namespace Clipper2Lib
const int non_pair_error_i = 4; // non-fatal
const int range_error_i = 64;
#ifndef PI
static const double PI = 3.141592653589793238;
#endif
static const int64_t MAX_COORD = INT64_MAX >> 2;
static const int64_t MIN_COORD = -MAX_COORD;
static const int64_t INVALID = INT64_MAX;
@ -558,38 +560,22 @@ namespace Clipper2Lib
}
template<typename T>
inline Path<T> StripDuplicates(const Path<T>& path, bool is_closed_path)
inline void StripDuplicates( Path<T>& path, bool is_closed_path)
{
if (path.size() == 0) return Path<T>();
Path<T> result;
result.reserve(path.size());
typename Path<T>::const_iterator path_iter = path.cbegin();
Point<T> first_pt = *path_iter++, last_pt = first_pt;
result.push_back(first_pt);
for (; path_iter != path.cend(); ++path_iter)
{
if (*path_iter != last_pt)
{
last_pt = *path_iter;
result.push_back(last_pt);
}
}
if (!is_closed_path) return result;
while (result.size() > 1 && result.back() == first_pt) result.pop_back();
return result;
//https://stackoverflow.com/questions/1041620/whats-the-most-efficient-way-to-erase-duplicates-and-sort-a-vector#:~:text=Let%27s%20compare%20three%20approaches%3A
path.erase(std::unique(path.begin(), path.end()),path.end());
if (is_closed_path)
while (path.size() > 1 && path.back() == path.front()) path.pop_back();
}
template<typename T>
inline Paths<T> StripDuplicates(const Paths<T>& paths, bool is_closed_path)
inline void StripDuplicates( Paths<T>& paths, bool is_closed_path)
{
Paths<T> result;
result.reserve(paths.size());
for (typename Paths<T>::const_iterator paths_citer = paths.cbegin();
paths_citer != paths.cend(); ++paths_citer)
for (typename Paths<T>::iterator paths_citer = paths.begin();
paths_citer != paths.end(); ++paths_citer)
{
result.push_back(StripDuplicates(*paths_citer, is_closed_path));
StripDuplicates(*paths_citer, is_closed_path);
}
return result;
}
// Miscellaneous ------------------------------------------------------------

View File

@ -1,6 +1,6 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 2 March 2023 *
* Date : 22 April 2023 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2023 *
* Purpose : This is the main polygon clipping module *
@ -10,7 +10,7 @@
#ifndef CLIPPER_ENGINE_H
#define CLIPPER_ENGINE_H
constexpr auto CLIPPER2_VERSION = "1.2.1";
constexpr auto CLIPPER2_VERSION = "1.2.2";
#include <cstdlib>
#include <iostream>
@ -260,8 +260,8 @@ namespace Clipper2Lib {
bool ExecuteInternal(ClipType ct, FillRule ft, bool use_polytrees);
void CleanCollinear(OutRec* outrec);
bool CheckBounds(OutRec* outrec);
bool CheckSplitOwner(OutRec* outrec);
void RecursiveCheckOwners(OutRec* outrec, PolyPath* polypath);
void DeepCheckOwners(OutRec* outrec, PolyPath* polypath);
#ifdef USINGZ
ZCallback64 zCallback_ = nullptr;
void SetZ(const Active& e1, const Active& e2, Point64& pt);

View File

@ -1,6 +1,6 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 22 March 2023 *
* Date : 23 March 2023 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2023 *
* Purpose : This module exports the Clipper2 Library (ie DLL/so) *
@ -157,14 +157,14 @@ EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths,
int precision = 2, double miter_limit = 2.0,
double arc_tolerance = 0.0, bool reverse_solution = false);
// RectClip & RectClipLines:
EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect,
// ExecuteRectClip & ExecuteRectClipLines:
EXTERN_DLL_EXPORT CPaths64 ExecuteRectClip64(const CRect64& rect,
const CPaths64 paths, bool convex_only = false);
EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect,
EXTERN_DLL_EXPORT CPathsD ExecuteRectClipD(const CRectD& rect,
const CPathsD paths, int precision = 2, bool convex_only = false);
EXTERN_DLL_EXPORT CPaths64 RectClipLines64(const CRect64& rect,
EXTERN_DLL_EXPORT CPaths64 ExecuteRectClipLines64(const CRect64& rect,
const CPaths64 paths);
EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect,
EXTERN_DLL_EXPORT CPathsD ExecuteRectClipLinesD(const CRectD& rect,
const CPathsD paths, int precision = 2);
//////////////////////////////////////////////////////
@ -381,7 +381,7 @@ EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths,
return CreateCPathsD(result, 1/scale);
}
EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect,
EXTERN_DLL_EXPORT CPaths64 ExecuteRectClip64(const CRect64& rect,
const CPaths64 paths, bool convex_only)
{
if (CRectIsEmpty(rect) || !paths) return nullptr;
@ -392,7 +392,7 @@ EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect,
return CreateCPaths64(result);
}
EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect,
EXTERN_DLL_EXPORT CPathsD ExecuteRectClipD(const CRectD& rect,
const CPathsD paths, int precision, bool convex_only)
{
if (CRectIsEmpty(rect) || !paths) return nullptr;
@ -407,7 +407,7 @@ EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect,
return CreateCPathsD(result, 1/scale);
}
EXTERN_DLL_EXPORT CPaths64 RectClipLines64(const CRect64& rect,
EXTERN_DLL_EXPORT CPaths64 ExecuteRectClipLines64(const CRect64& rect,
const CPaths64 paths)
{
if (CRectIsEmpty(rect) || !paths) return nullptr;
@ -418,7 +418,7 @@ EXTERN_DLL_EXPORT CPaths64 RectClipLines64(const CRect64& rect,
return CreateCPaths64(result);
}
EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect,
EXTERN_DLL_EXPORT CPathsD ExecuteRectClipLinesD(const CRectD& rect,
const CPathsD paths, int precision)
{
if (CRectIsEmpty(rect) || !paths) return nullptr;

View File

@ -1,6 +1,6 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 9 February 2023 *
* Date : 21 April 2023 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2023 *
* Purpose : This module provides a simple interface to the Clipper Library *
@ -24,7 +24,7 @@ namespace Clipper2Lib {
inline Paths64 BooleanOp(ClipType cliptype, FillRule fillrule,
const Paths64& subjects, const Paths64& clips)
{
{
Paths64 result;
Clipper64 clipper;
clipper.AddSubject(subjects);
@ -58,7 +58,7 @@ namespace Clipper2Lib {
}
inline void BooleanOp(ClipType cliptype, FillRule fillrule,
const PathsD& subjects, const PathsD& clips,
const PathsD& subjects, const PathsD& clips,
PolyTreeD& polytree, int precision = 2)
{
polytree.Clear();
@ -75,7 +75,7 @@ namespace Clipper2Lib {
{
return BooleanOp(ClipType::Intersection, fillrule, subjects, clips);
}
inline PathsD Intersect(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2)
{
return BooleanOp(ClipType::Intersection, fillrule, subjects, clips, decimal_prec);
@ -145,7 +145,7 @@ namespace Clipper2Lib {
}
inline PathsD InflatePaths(const PathsD& paths, double delta,
JoinType jt, EndType et, double miter_limit = 2.0,
JoinType jt, EndType et, double miter_limit = 2.0,
int precision = 2, double arc_tolerance = 0.0)
{
int error_code = 0;
@ -197,24 +197,24 @@ namespace Clipper2Lib {
return result;
}
inline Paths64 ExecuteRectClip(const Rect64& rect,
const Paths64& paths, bool convex_only = false)
inline Paths64 ExecuteRectClip(const Rect64& rect,
const Paths64& paths, bool convex_only)
{
if (rect.IsEmpty() || paths.empty()) return Paths64();
class RectClip rc(rect);
RectClip rc(rect);
return rc.Execute(paths, convex_only);
}
inline Paths64 ExecuteRectClip(const Rect64& rect,
const Path64& path, bool convex_only = false)
const Path64& path, bool convex_only)
{
if (rect.IsEmpty() || path.empty()) return Paths64();
class RectClip rc(rect);
RectClip rc(rect);
return rc.Execute(Paths64{ path }, convex_only);
}
inline PathsD ExecuteRectClip(const RectD& rect,
const PathsD& paths, bool convex_only = false, int precision = 2)
const PathsD& paths, bool convex_only, int precision = 2)
{
if (rect.IsEmpty() || paths.empty()) return PathsD();
int error_code = 0;
@ -222,15 +222,15 @@ namespace Clipper2Lib {
if (error_code) return PathsD();
const double scale = std::pow(10, precision);
Rect64 r = ScaleRect<int64_t, double>(rect, scale);
class RectClip rc(r);
RectClip rc(r);
Paths64 pp = ScalePaths<int64_t, double>(paths, scale, error_code);
if (error_code) return PathsD(); // ie: error_code result is lost
if (error_code) return PathsD(); // ie: error_code result is lost
return ScalePaths<double, int64_t>(
rc.Execute(pp, convex_only), 1 / scale, error_code);
}
inline PathsD ExecuteRectClip(const RectD& rect,
const PathD& path, bool convex_only = false, int precision = 2)
const PathD& path, bool convex_only, int precision = 2)
{
return ExecuteRectClip(rect, PathsD{ path }, convex_only, precision);
}
@ -238,7 +238,7 @@ namespace Clipper2Lib {
inline Paths64 ExecuteRectClipLines(const Rect64& rect, const Paths64& lines)
{
if (rect.IsEmpty() || lines.empty()) return Paths64();
class RectClipLines rcl(rect);
RectClipLines rcl(rect);
return rcl.Execute(lines);
}
@ -247,11 +247,6 @@ namespace Clipper2Lib {
return ExecuteRectClipLines(rect, Paths64{ line });
}
inline PathsD ExecuteRectClipLines(const RectD& rect, const PathD& line, int precision = 2)
{
return ExecuteRectClip(rect, PathsD{ line }, precision);
}
inline PathsD ExecuteRectClipLines(const RectD& rect, const PathsD& lines, int precision = 2)
{
if (rect.IsEmpty() || lines.empty()) return PathsD();
@ -260,13 +255,18 @@ namespace Clipper2Lib {
if (error_code) return PathsD();
const double scale = std::pow(10, precision);
Rect64 r = ScaleRect<int64_t, double>(rect, scale);
class RectClipLines rcl(r);
RectClipLines rcl(r);
Paths64 p = ScalePaths<int64_t, double>(lines, scale, error_code);
if (error_code) return PathsD();
p = rcl.Execute(p);
return ScalePaths<double, int64_t>(p, 1 / scale, error_code);
}
inline PathsD ExecuteRectClipLines(const RectD& rect, const PathD& line, int precision = 2)
{
return ExecuteRectClipLines(rect, PathsD{ line }, precision);
}
namespace details
{
@ -316,7 +316,7 @@ namespace Clipper2Lib {
return true;
}
static void OutlinePolyPath(std::ostream& os,
static void OutlinePolyPath(std::ostream& os,
bool isHole, size_t count, const std::string& preamble)
{
std::string plural = (count == 1) ? "." : "s.";
@ -366,10 +366,11 @@ namespace Clipper2Lib {
}
}
} // end details namespace
} // end details namespace
inline std::ostream& operator<< (std::ostream& os, const PolyTree64& pp)
{
os << std::endl << "Polytree root" << std::endl;
PolyPath64List::const_iterator it = pp.begin();
for (; it < pp.end() - 1; ++it)
details::OutlinePolyPath64(os, **it, " ", false);
@ -409,7 +410,7 @@ namespace Clipper2Lib {
inline bool CheckPolytreeFullyContainsChildren(const PolyTree64& polytree)
{
for (const auto& child : polytree)
if (child->Count() > 0 &&
if (child->Count() > 0 &&
!details::PolyPath64ContainsChildren(*child))
return false;
return true;
@ -574,12 +575,12 @@ namespace Clipper2Lib {
double cp = std::abs(CrossProduct(pt1, pt2, pt3));
return (cp * cp) / (DistanceSqr(pt1, pt2) * DistanceSqr(pt2, pt3)) < sin_sqrd_min_angle_rads;
}
template <typename T>
inline Path<T> Ellipse(const Rect<T>& rect, int steps = 0)
{
return Ellipse(rect.MidPoint(),
static_cast<double>(rect.Width()) *0.5,
return Ellipse(rect.MidPoint(),
static_cast<double>(rect.Width()) *0.5,
static_cast<double>(rect.Height()) * 0.5, steps);
}
@ -620,7 +621,7 @@ namespace Clipper2Lib {
return Sqr(a * d - c * b) / (c * c + d * d);
}
inline size_t GetNext(size_t current, size_t high,
inline size_t GetNext(size_t current, size_t high,
const std::vector<bool>& flags)
{
++current;
@ -631,7 +632,7 @@ namespace Clipper2Lib {
return current;
}
inline size_t GetPrior(size_t current, size_t high,
inline size_t GetPrior(size_t current, size_t high,
const std::vector<bool>& flags)
{
if (current == 0) current = high;
@ -644,8 +645,8 @@ namespace Clipper2Lib {
}
template <typename T>
inline Path<T> SimplifyPath(const Path<T> path,
double epsilon, bool isOpenPath = false)
inline Path<T> SimplifyPath(const Path<T> path,
double epsilon, bool isClosedPath = true)
{
const size_t len = path.size(), high = len -1;
const double epsSqr = Sqr(epsilon);
@ -654,16 +655,16 @@ namespace Clipper2Lib {
std::vector<bool> flags(len);
std::vector<double> distSqr(len);
size_t prior = high, curr = 0, start, next, prior2, next2;
if (isOpenPath)
{
distSqr[0] = MAX_DBL;
distSqr[high] = MAX_DBL;
}
else
if (isClosedPath)
{
distSqr[0] = PerpendicDistFromLineSqrd(path[0], path[high], path[1]);
distSqr[high] = PerpendicDistFromLineSqrd(path[high], path[0], path[high - 1]);
}
else
{
distSqr[0] = MAX_DBL;
distSqr[high] = MAX_DBL;
}
for (size_t i = 1; i < high; ++i)
distSqr[i] = PerpendicDistFromLineSqrd(path[i], path[i - 1], path[i + 1]);
@ -678,7 +679,7 @@ namespace Clipper2Lib {
} while (curr != start && distSqr[curr] > epsSqr);
if (curr == start) break;
}
prior = GetPrior(curr, high, flags);
next = GetNext(curr, high, flags);
if (next == prior) break;
@ -689,7 +690,7 @@ namespace Clipper2Lib {
next = GetNext(next, high, flags);
next2 = GetNext(next, high, flags);
distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]);
if (next != high || !isOpenPath)
if (next != high || isClosedPath)
distSqr[next] = PerpendicDistFromLineSqrd(path[next], path[curr], path[next2]);
curr = next;
}
@ -700,7 +701,7 @@ namespace Clipper2Lib {
next = GetNext(next, high, flags);
prior2 = GetPrior(prior, high, flags);
distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]);
if (prior != 0 || !isOpenPath)
if (prior != 0 || isClosedPath)
distSqr[prior] = PerpendicDistFromLineSqrd(path[prior], path[prior2], path[curr]);
}
}
@ -712,13 +713,13 @@ namespace Clipper2Lib {
}
template <typename T>
inline Paths<T> SimplifyPaths(const Paths<T> paths,
double epsilon, bool isOpenPath = false)
inline Paths<T> SimplifyPaths(const Paths<T> paths,
double epsilon, bool isClosedPath = true)
{
Paths<T> result;
result.reserve(paths.size());
for (const auto& path : paths)
result.push_back(SimplifyPath(path, epsilon, isOpenPath));
result.push_back(SimplifyPath(path, epsilon, isClosedPath));
return result;
}

View File

@ -1,6 +1,6 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 19 March 2023 *
* Date : 23 April 2023 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2023 *
* Purpose : This is the main polygon clipping module *
@ -594,6 +594,9 @@ namespace Clipper2Lib {
//for each path create a circular double linked list of vertices
Vertex* v0 = v, * curr_v = v, * prev_v = nullptr;
if (path.empty())
continue;
v->prev = nullptr;
int cnt = 0;
for (const Point64& pt : path)
@ -1533,7 +1536,7 @@ namespace Clipper2Lib {
InsertScanline(e->top.y);
CheckJoinLeft(*e, e->bot);
CheckJoinRight(*e, e->bot);
CheckJoinRight(*e, e->bot, true); // (#500)
}
Active* FindEdgeWithMatchingLocMin(Active* e)
@ -2124,7 +2127,11 @@ namespace Clipper2Lib {
else if (Path1InsidePath2(or1->pts, or2->pts))
SetOwner(or1, or2);
else
{
if (!or1->splits) or1->splits = new OutRecList();
or1->splits->push_back(or2); //(#498)
or2->owner = or1;
}
}
else
or2->owner = or1;
@ -2635,10 +2642,10 @@ namespace Clipper2Lib {
const Point64& pt, bool check_curr_x)
{
Active* prev = e.prev_in_ael;
if (IsOpen(e) || !IsHotEdge(e) || !prev ||
IsOpen(*prev) || !IsHotEdge(*prev) ||
pt.y < e.top.y + 2 || pt.y < prev->top.y + 2) // avoid trivial joins
return;
if (IsOpen(e) || !IsHotEdge(e) || !prev ||
IsOpen(*prev) || !IsHotEdge(*prev)) return;
if ((pt.y < e.top.y + 2 || pt.y < prev->top.y + 2) &&
((e.bot.y > pt.y) || (prev->bot.y > pt.y))) return; // avoid trivial joins
if (check_curr_x)
{
@ -2661,10 +2668,10 @@ namespace Clipper2Lib {
const Point64& pt, bool check_curr_x)
{
Active* next = e.next_in_ael;
if (IsOpen(e) || !IsHotEdge(e) ||
!next || IsOpen(*next) || !IsHotEdge(*next) ||
pt.y < e.top.y +2 || pt.y < next->top.y +2) // avoids trivial joins
return;
if (IsOpen(e) || !IsHotEdge(e) ||
!next || IsOpen(*next) || !IsHotEdge(*next)) return;
if ((pt.y < e.top.y +2 || pt.y < next->top.y +2) &&
((e.bot.y > pt.y) || (next->bot.y > pt.y))) return; // avoid trivial joins
if (check_curr_x)
{
@ -2679,6 +2686,7 @@ namespace Clipper2Lib {
JoinOutrecPaths(e, *next);
else
JoinOutrecPaths(*next, e);
e.join_with = JoinWith::Right;
next->join_with = JoinWith::Left;
}
@ -2755,6 +2763,23 @@ namespace Clipper2Lib {
return true;
}
bool ClipperBase::CheckSplitOwner(OutRec* outrec)
{
for (auto s : *outrec->owner->splits)
{
OutRec* split = GetRealOutRec(s);
if (split && split != outrec &&
split != outrec->owner && CheckBounds(split) &&
split->bounds.Contains(outrec->bounds) &&
Path1InsidePath2(outrec->pts, split->pts))
{
outrec->owner = split; //found in split
return true;
}
}
return false;
}
void ClipperBase::RecursiveCheckOwners(OutRec* outrec, PolyPath* polypath)
{
// pre-condition: outrec will have valid bounds
@ -2762,52 +2787,25 @@ namespace Clipper2Lib {
if (outrec->polypath || outrec->bounds.IsEmpty()) return;
while (outrec->owner &&
(!outrec->owner->pts || !CheckBounds(outrec->owner)))
outrec->owner = outrec->owner->owner;
if (outrec->owner && !outrec->owner->polypath)
RecursiveCheckOwners(outrec->owner, polypath);
while (outrec->owner)
if (outrec->owner->bounds.Contains(outrec->bounds) &&
Path1InsidePath2(outrec->pts, outrec->owner->pts))
break; // found - owner contain outrec!
else
outrec->owner = outrec->owner->owner;
{
if (outrec->owner->splits && CheckSplitOwner(outrec)) break;
if (outrec->owner->pts && CheckBounds(outrec->owner) &&
outrec->owner->bounds.Contains(outrec->bounds) &&
Path1InsidePath2(outrec->pts, outrec->owner->pts)) break;
outrec->owner = outrec->owner->owner;
}
if (outrec->owner)
{
if (!outrec->owner->polypath)
RecursiveCheckOwners(outrec->owner, polypath);
outrec->polypath = outrec->owner->polypath->AddChild(outrec->path);
}
else
outrec->polypath = polypath->AddChild(outrec->path);
}
void ClipperBase::DeepCheckOwners(OutRec* outrec, PolyPath* polypath)
{
RecursiveCheckOwners(outrec, polypath);
while (outrec->owner && outrec->owner->splits)
{
OutRec* split = nullptr;
for (auto s : *outrec->owner->splits)
{
split = GetRealOutRec(s);
if (split && split != outrec &&
split != outrec->owner && CheckBounds(split) &&
split->bounds.Contains(outrec->bounds) &&
Path1InsidePath2(outrec->pts, split->pts))
{
RecursiveCheckOwners(split, polypath);
outrec->owner = split; //found in split
break; // inner 'for' loop
}
else
split = nullptr;
}
if (!split) break;
}
}
void Clipper64::BuildPaths64(Paths64& solutionClosed, Paths64* solutionOpen)
{
solutionClosed.resize(0);
@ -2866,7 +2864,7 @@ namespace Clipper2Lib {
}
if (CheckBounds(outrec))
DeepCheckOwners(outrec, &polytree);
RecursiveCheckOwners(outrec, &polytree);
}
}
@ -2969,7 +2967,7 @@ namespace Clipper2Lib {
}
if (CheckBounds(outrec))
DeepCheckOwners(outrec, &polytree);
RecursiveCheckOwners(outrec, &polytree);
}
}

View File

@ -1,6 +1,6 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 22 March 2023 *
* Date : 8 April 2023 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2023 *
* Purpose : Path Offset (Inflate/Shrink) *
@ -302,13 +302,7 @@ void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t& k)
if (sin_a > 1.0) sin_a = 1.0;
else if (sin_a < -1.0) sin_a = -1.0;
if (cos_a > 0.99) // almost straight - less than 8 degrees
{
group.path.push_back(GetPerpendic(path[j], norms[k], group_delta_));
if (cos_a < 0.9998) // greater than 1 degree (#424)
group.path.push_back(GetPerpendic(path[j], norms[j], group_delta_)); // (#418)
}
else if (cos_a > -0.99 && (sin_a * group_delta_ < 0))
if (cos_a > -0.99 && (sin_a * group_delta_ < 0))
{
// is concave
group.path.push_back(GetPerpendic(path[j], norms[k], group_delta_));
@ -316,21 +310,21 @@ void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t& k)
// path reversals are fully cleaned with the trailing clipper
group.path.push_back(path[j]); // (#405)
group.path.push_back(GetPerpendic(path[j], norms[j], group_delta_));
}
else if (join_type_ == JoinType::Round)
DoRound(group, path, j, k, std::atan2(sin_a, cos_a));
}
else if (join_type_ == JoinType::Miter)
{
// miter unless the angle is so acute the miter would exceeds ML
if (cos_a > temp_lim_ - 1) DoMiter(group, path, j, k, cos_a);
else DoSquare(group, path, j, k);
}
// don't bother squaring angles that deviate < ~20 degrees because
// squaring will be indistinguishable from mitering and just be a lot slower
else if (cos_a > 0.9)
else if (cos_a > 0.9998)
// almost straight - less than 1 degree (#424)
DoMiter(group, path, j, k, cos_a);
else
else if (cos_a > 0.99 || join_type_ == JoinType::Square)
//angle less than 8 degrees or squared joins
DoSquare(group, path, j, k);
else
DoRound(group, path, j, k, std::atan2(sin_a, cos_a));
k = j;
}
@ -481,10 +475,11 @@ void ClipperOffset::DoGroupOffset(Group& group)
bool is_joined =
(end_type_ == EndType::Polygon) ||
(end_type_ == EndType::Joined);
Paths64::const_iterator path_iter;
for(path_iter = group.paths_in.cbegin(); path_iter != group.paths_in.cend(); ++path_iter)
Paths64::iterator path_iter;
for(path_iter = group.paths_in.begin(); path_iter != group.paths_in.end(); ++path_iter)
{
Path64 path = StripDuplicates(*path_iter, is_joined);
auto path = *path_iter;
StripDuplicates(path, is_joined);
Path64::size_type cnt = path.size();
if (cnt == 0 || ((cnt < 3) && group.end_type == EndType::Polygon))
continue;

View File

@ -1,6 +1,6 @@
/*******************************************************************************
* Author : Angus Johnson *
* Date : 14 February 2023 *
* Date : 22 April 2023 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2023 *
* Purpose : FAST rectangular clipping *
@ -619,7 +619,7 @@ namespace Clipper2Lib {
p1 = cw[i];
if (!p1 || p1->next == p1->prev)
{
cw[i++]->edge = nullptr;
cw[i++] = nullptr;
j = 0;
continue;
}
@ -819,8 +819,8 @@ namespace Clipper2Lib {
Paths64 result;
if (rect_.IsEmpty()) return result;
for (const auto& path : paths)
{
for (const Path64& path : paths)
{
if (path.size() < 3) continue;
path_bounds_ = GetBounds(path);
if (!rect_.Intersects(path_bounds_))
@ -867,9 +867,7 @@ namespace Clipper2Lib {
for (const auto& path : paths)
{
if (path.size() < 2) continue;
Rect64 pathrec = GetBounds(path);
if (!rect_.Intersects(pathrec)) continue;
ExecuteInternal(path);