diff --git a/common/advanced_config.cpp b/common/advanced_config.cpp index 2e9bf1c92b..5f4f12dce1 100644 --- a/common/advanced_config.cpp +++ b/common/advanced_config.cpp @@ -64,6 +64,7 @@ namespace AC_KEYS static const wxChar IncrementalConnectivity[] = wxT( "IncrementalConnectivity" ); static const wxChar Use3DConnexionDriver[] = wxT( "3DConnexionDriver" ); static const wxChar ExtraFillMargin[] = wxT( "ExtraFillMargin" ); +static const wxChar EnableCreepageDRC[] = wxT( "EnableCreepageDRC" ); static const wxChar DRCEpsilon[] = wxT( "DRCEpsilon" ); static const wxChar DRCSliverWidthTolerance[] = wxT( "DRCSliverWidthTolerance" ); static const wxChar DRCSliverMinimumLength[] = wxT( "DRCSliverMinimumLength" ); @@ -222,6 +223,7 @@ ADVANCED_CFG::ADVANCED_CFG() m_DrawTriangulationOutlines = false; m_ExtraClearance = 0.0005; + m_EnableCreepageDRC = false; m_DRCEpsilon = 0.0005; // 0.5um is small enough not to materially violate // any constraints. m_SliverWidthTolerance = 0.08; @@ -338,6 +340,10 @@ void ADVANCED_CFG::loadSettings( wxConfigBase& aCfg ) &m_ExtraClearance, m_ExtraClearance, 0.0, 1.0 ) ); + configParams.push_back( new PARAM_CFG_BOOL( true, AC_KEYS::EnableCreepageDRC, + &m_EnableCreepageDRC, m_EnableCreepageDRC ) ); + + configParams.push_back( new PARAM_CFG_DOUBLE( true, AC_KEYS::DRCEpsilon, &m_DRCEpsilon, m_DRCEpsilon, 0.0, 1.0 ) ); diff --git a/common/drc_rules.keywords b/common/drc_rules.keywords index 7d03b25b73..cb27ad1fc1 100644 --- a/common/drc_rules.keywords +++ b/common/drc_rules.keywords @@ -7,6 +7,7 @@ condition connection_width constraint courtyard_clearance +creepage diff_pair_gap diff_pair_uncoupled disallow diff --git a/common/pcb.keywords b/common/pcb.keywords index 13e0d96a4f..5bb250e658 100644 --- a/common/pcb.keywords +++ b/common/pcb.keywords @@ -88,6 +88,7 @@ convexhull copper_line_width copper_text_dims courtyard_line_width +creepage data date defaults diff --git a/include/advanced_config.h b/include/advanced_config.h index ca57c1f445..764cdd03ff 100644 --- a/include/advanced_config.h +++ b/include/advanced_config.h @@ -124,6 +124,14 @@ public: */ double m_ExtraClearance; + /** + * Enable the DRC creepage check + * + * Setting name: "EnableCreepageDRC" + * Default value: false + */ + bool m_EnableCreepageDRC; + /** * Epsilon for DRC tests. * diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt index 318f590240..9bc6872e72 100644 --- a/pcbnew/CMakeLists.txt +++ b/pcbnew/CMakeLists.txt @@ -256,6 +256,7 @@ set( PCBNEW_DRC_SRCS drc/drc_test_provider.cpp drc/drc_test_provider_annular_width.cpp drc/drc_test_provider_disallow.cpp + drc/drc_test_provider_creepage.cpp drc/drc_test_provider_connectivity.cpp drc/drc_test_provider_connection_width.cpp drc/drc_test_provider_copper_clearance.cpp diff --git a/pcbnew/dialogs/panel_setup_rules.cpp b/pcbnew/dialogs/panel_setup_rules.cpp index 38f21df377..aa7feb1c67 100644 --- a/pcbnew/dialogs/panel_setup_rules.cpp +++ b/pcbnew/dialogs/panel_setup_rules.cpp @@ -461,6 +461,7 @@ void PANEL_SETUP_RULES::onScintillaCharAdded( wxStyledTextEvent &aEvent ) "clearance|" "connection_width|" "courtyard_clearance|" + "creepage|" "diff_pair_gap|" "diff_pair_uncoupled|" "disallow|" diff --git a/pcbnew/dialogs/panel_setup_rules_help.md b/pcbnew/dialogs/panel_setup_rules_help.md index a30008fea8..c4999509b1 100644 --- a/pcbnew/dialogs/panel_setup_rules_help.md +++ b/pcbnew/dialogs/panel_setup_rules_help.md @@ -27,6 +27,7 @@ | `assertion` | "<expression>" | Checks the given expression.<br> | | `clearance` | min | Specifies the **electrical** clearance between copper objects of different nets. (See `physical_clearance` if you wish to specify clearance between objects regardless of net.)<br><br>To allow copper objects to overlap (collide), create a `clearance` constraint with the `min` value less than zero (for example, `-1`).<br> | | `courtyard_clearance` | min | Checks the clearance between footprint courtyards and generates an error if any two courtyards are closer than the `min` distance. If a footprint does not have a courtyard shape, no errors will be generated from this constraint.<br> | +<!--| `creepage` | min | Specifies the **electrical** creepage between copper objects of different nets.<br> --> | `diff_pair_gap` | min/opt/max | Checks the gap between coupled tracks in a differential pair. Coupled tracks are segments that are parallel to each other. Differential pair gap is not tested on uncoupled portions of a differential pair (for example, the fanout from a component).<br> | | `diff_pair_uncoupled` | max | Checks the distance that a differential pair track is routed uncoupled from the other polarity track in the pair (for example, where the pair fans out from a component, or becomes uncoupled to pass around another object such as a via).<br> | | `disallow` | `track`<br>`via`<br>`micro_via`<br>`buried_via`<br>`pad`<br>`zone`<br>`text`<br>`graphic`<br>`hole`<br>`footprint`<br> | Specify one or more object types to disallow, separated by spaces. For example, `(constraint disallow track)` or `(constraint disallow track via pad)`. If an object of this type matches the rule condition, a DRC error will be created.<br><br>This constraint is essentially the same as a keepout rule area, but can be used to create more specific keepout restrictions.<br> | diff --git a/pcbnew/dialogs/panel_setup_rules_help_md.h b/pcbnew/dialogs/panel_setup_rules_help_md.h index c322a2d952..4a46f0ac8b 100644 --- a/pcbnew/dialogs/panel_setup_rules_help_md.h +++ b/pcbnew/dialogs/panel_setup_rules_help_md.h @@ -28,6 +28,7 @@ _HKI( "### Top-level Clauses\n" "| `assertion` | \"<expression>\" | Checks the given expression.<br> |\n" "| `clearance` | min | Specifies the **electrical** clearance between copper objects of different nets. (See `physical_clearance` if you wish to specify clearance between objects regardless of net.)<br><br>To allow copper objects to overlap (collide), create a `clearance` constraint with the `min` value less than zero (for example, `-1`).<br> |\n" "| `courtyard_clearance` | min | Checks the clearance between footprint courtyards and generates an error if any two courtyards are closer than the `min` distance. If a footprint does not have a courtyard shape, no errors will be generated from this constraint.<br> |\n" +"<!-- | `creepage` | min | Specifies the **electrical** creepage between copper objects of different nets.<br> --> \n" "| `diff_pair_gap` | min/opt/max | Checks the gap between coupled tracks in a differential pair. Coupled tracks are segments that are parallel to each other. Differential pair gap is not tested on uncoupled portions of a differential pair (for example, the fanout from a component).<br> |\n" "| `diff_pair_uncoupled` | max | Checks the distance that a differential pair track is routed uncoupled from the other polarity track in the pair (for example, where the pair fans out from a component, or becomes uncoupled to pass around another object such as a via).<br> |\n" "| `disallow` | `track`<br>`via`<br>`micro_via`<br>`buried_via`<br>`pad`<br>`zone`<br>`text`<br>`graphic`<br>`hole`<br>`footprint`<br> | Specify one or more object types to disallow, separated by spaces. For example, `(constraint disallow track)` or `(constraint disallow track via pad)`. If an object of this type matches the rule condition, a DRC error will be created.<br><br>This constraint is essentially the same as a keepout rule area, but can be used to create more specific keepout restrictions.<br> |\n" diff --git a/pcbnew/drc/drc_engine.cpp b/pcbnew/drc/drc_engine.cpp index 1f5252d7d3..13a1fc689b 100644 --- a/pcbnew/drc/drc_engine.cpp +++ b/pcbnew/drc/drc_engine.cpp @@ -593,7 +593,8 @@ void DRC_ENGINE::InitEngine( const wxFileName& aRulePath ) } -void DRC_ENGINE::RunTests( EDA_UNITS aUnits, bool aReportAllTrackErrors, bool aTestFootprints ) +void DRC_ENGINE::RunTests( EDA_UNITS aUnits, bool aReportAllTrackErrors, bool aTestFootprints, + BOARD_COMMIT* aCommit ) { SetUserUnits( aUnits ); @@ -625,6 +626,7 @@ void DRC_ENGINE::RunTests( EDA_UNITS aUnits, bool aReportAllTrackErrors, bool aT for( DRC_TEST_PROVIDER* provider : m_testProviders ) { ReportAux( wxString::Format( wxT( "Run DRC provider: '%s'" ), provider->GetName() ) ); + provider->SetCommit( aCommit ); if( !provider->RunTests( aUnits ) ) break; @@ -906,7 +908,11 @@ DRC_CONSTRAINT DRC_ENGINE::EvalRules( DRC_CONSTRAINT_T aConstraintType, const BO EscapeHTML( c->constraint.GetName() ), MessageTextFromValue( c->constraint.m_Value.Min() ) ) ) break; - + case CREEPAGE_CONSTRAINT: + REPORT( wxString::Format( _( "Checking %s creepage: %s." ), + EscapeHTML( c->constraint.GetName() ), + MessageTextFromValue( c->constraint.m_Value.Min() ) ) ) + break; case MAX_UNCOUPLED_CONSTRAINT: REPORT( wxString::Format( _( "Checking %s max uncoupled length: %s." ), EscapeHTML( c->constraint.GetName() ), diff --git a/pcbnew/drc/drc_engine.h b/pcbnew/drc/drc_engine.h index 708be67882..2253be9480 100644 --- a/pcbnew/drc/drc_engine.h +++ b/pcbnew/drc/drc_engine.h @@ -34,6 +34,7 @@ #include <drc/drc_rule.h> +class BOARD_COMMIT; class BOARD_DESIGN_SETTINGS; class DRC_TEST_PROVIDER; class PCB_EDIT_FRAME; @@ -142,7 +143,8 @@ public: /** * Run the DRC tests. */ - void RunTests( EDA_UNITS aUnits, bool aReportAllTrackErrors, bool aTestFootprints ); + void RunTests( EDA_UNITS aUnits, bool aReportAllTrackErrors, bool aTestFootprints, + BOARD_COMMIT* aCommit = nullptr ); bool IsErrorLimitExceeded( int error_code ); diff --git a/pcbnew/drc/drc_item.cpp b/pcbnew/drc/drc_item.cpp index 0604cce4ca..a4be1724fb 100644 --- a/pcbnew/drc/drc_item.cpp +++ b/pcbnew/drc/drc_item.cpp @@ -70,6 +70,10 @@ DRC_ITEM DRC_ITEM::clearance( DRCE_CLEARANCE, _( "Clearance violation" ), wxT( "clearance" ) ); +DRC_ITEM DRC_ITEM::creepage( DRCE_CREEPAGE, + _( "Creepage violation" ), + wxT( "creepage" ) ); + DRC_ITEM DRC_ITEM::tracksCrossing( DRCE_TRACKS_CROSSING, _( "Tracks crossing" ), wxT( "tracks_crossing" ) ); @@ -286,6 +290,7 @@ std::vector<std::reference_wrapper<RC_ITEM>> DRC_ITEM::allItemTypes( DRC_ITEM::shortingItems, DRC_ITEM::tracksCrossing, DRC_ITEM::clearance, + DRC_ITEM::creepage, DRC_ITEM::viaDangling, DRC_ITEM::trackDangling, DRC_ITEM::starvedThermal, @@ -363,6 +368,7 @@ std::shared_ptr<DRC_ITEM> DRC_ITEM::Create( int aErrorCode ) case DRCE_ALLOWED_ITEMS: return std::make_shared<DRC_ITEM>( itemsNotAllowed ); case DRCE_TEXT_ON_EDGECUTS: return std::make_shared<DRC_ITEM>( textOnEdgeCuts ); case DRCE_CLEARANCE: return std::make_shared<DRC_ITEM>( clearance ); + case DRCE_CREEPAGE: return std::make_shared<DRC_ITEM>( creepage ); case DRCE_TRACKS_CROSSING: return std::make_shared<DRC_ITEM>( tracksCrossing ); case DRCE_EDGE_CLEARANCE: return std::make_shared<DRC_ITEM>( edgeClearance ); case DRCE_ZONES_INTERSECT: return std::make_shared<DRC_ITEM>( zonesIntersect ); diff --git a/pcbnew/drc/drc_item.h b/pcbnew/drc/drc_item.h index 4efe624a26..a6f063745c 100644 --- a/pcbnew/drc/drc_item.h +++ b/pcbnew/drc/drc_item.h @@ -41,6 +41,7 @@ enum PCB_DRC_CODE { DRCE_ALLOWED_ITEMS, // a disallowed item has been used DRCE_TEXT_ON_EDGECUTS, // text or dimension on Edge.Cuts layer DRCE_CLEARANCE, // items are too close together + DRCE_CREEPAGE, // items are too close together ( creepage ) DRCE_TRACKS_CROSSING, // tracks are crossing DRCE_EDGE_CLEARANCE, // a copper item is too close to the board edge DRCE_ZONES_INTERSECT, // copper zone outlines intersect @@ -178,6 +179,7 @@ private: static DRC_ITEM itemsNotAllowed; static DRC_ITEM textOnEdgeCuts; static DRC_ITEM clearance; + static DRC_ITEM creepage; static DRC_ITEM tracksCrossing; static DRC_ITEM edgeClearance; static DRC_ITEM zonesIntersect; diff --git a/pcbnew/drc/drc_rule.h b/pcbnew/drc/drc_rule.h index ce03c53161..ce02d0cb93 100644 --- a/pcbnew/drc/drc_rule.h +++ b/pcbnew/drc/drc_rule.h @@ -47,6 +47,7 @@ enum DRC_CONSTRAINT_T { NULL_CONSTRAINT = 0, CLEARANCE_CONSTRAINT, + CREEPAGE_CONSTRAINT, HOLE_CLEARANCE_CONSTRAINT, HOLE_TO_HOLE_CONSTRAINT, EDGE_CLEARANCE_CONSTRAINT, diff --git a/pcbnew/drc/drc_rule_parser.cpp b/pcbnew/drc/drc_rule_parser.cpp index 92f21b7788..0d012f851c 100644 --- a/pcbnew/drc/drc_rule_parser.cpp +++ b/pcbnew/drc/drc_rule_parser.cpp @@ -306,6 +306,7 @@ void DRC_RULES_PARSER::parseConstraint( DRC_RULE* aRule ) { case T_assertion: c.m_Type = ASSERTION_CONSTRAINT; break; case T_clearance: c.m_Type = CLEARANCE_CONSTRAINT; break; + case T_creepage: c.m_Type = CREEPAGE_CONSTRAINT; break; case T_hole_clearance: c.m_Type = HOLE_CLEARANCE_CONSTRAINT; break; case T_edge_clearance: c.m_Type = EDGE_CLEARANCE_CONSTRAINT; break; case T_hole_size: c.m_Type = HOLE_SIZE_CONSTRAINT; break; diff --git a/pcbnew/drc/drc_test_provider.h b/pcbnew/drc/drc_test_provider.h index a584bcddd5..1afcf98447 100644 --- a/pcbnew/drc/drc_test_provider.h +++ b/pcbnew/drc/drc_test_provider.h @@ -26,6 +26,7 @@ #define DRC_TEST_PROVIDER__H #include <board.h> +#include <board_commit.h> #include <pcb_marker.h> #include <functional> @@ -99,6 +100,9 @@ public: virtual const wxString GetName() const; virtual const wxString GetDescription() const; + BOARD_COMMIT* GetCommit() const { return m_commit; }; + void SetCommit( BOARD_COMMIT* aCommit ) { m_commit = aCommit; }; + protected: int forEachGeometryItem( const std::vector<KICAD_T>& aTypes, LSET aLayers, const std::function<bool(BOARD_ITEM*)>& aFunc ); @@ -133,6 +137,7 @@ protected: std::unordered_map<const DRC_RULE*, int> m_stats; bool m_isRuleDriven = true; std::mutex m_statsMutex; + BOARD_COMMIT* m_commit; }; #endif // DRC_TEST_PROVIDER__H diff --git a/pcbnew/drc/drc_test_provider_creepage.cpp b/pcbnew/drc/drc_test_provider_creepage.cpp new file mode 100644 index 0000000000..43fa3fb92c --- /dev/null +++ b/pcbnew/drc/drc_test_provider_creepage.cpp @@ -0,0 +1,3573 @@ +/* + * Copyright (C) 2024 KiCad Developers. + * Copyright (C) 2024 Fabien Corona f.corona<at>laposte.net + * + * 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 <common.h> +#include <macros.h> +#include <board_design_settings.h> +#include <footprint.h> +#include <pad.h> +#include <pcb_track.h> +#include <pcb_shape.h> +#include <zone.h> +#include <advanced_config.h> +#include <geometry/shape_rect.h> +#include <geometry/seg.h> +#include <geometry/shape_segment.h> +#include <drc/drc_engine.h> +#include <drc/drc_rtree.h> +#include <drc/drc_item.h> +#include <drc/drc_rule.h> +#include <drc/drc_test_provider_clearance_base.h> + +#include <geometry/shape_circle.h> + + +extern bool SegmentIntersectsBoard( VECTOR2I aP1, VECTOR2I aP2, std::vector<BOARD_ITEM*> aBe, + std::vector<const BOARD_ITEM*> aDontTestAgainst ); +bool segmentIntersectsArc( VECTOR2I p1, VECTOR2I p2, VECTOR2I center, double radius, + double startAngle, double endAngle ); + +struct path_connection +{ + VECTOR2D a1; + VECTOR2D a2; + double weight = -1; + bool m_show = true; + + bool m_forceA1concavityCheck = false; + bool m_forceA2concavityCheck = false; + + + /** @brief Test if a path is valid + * + * Check if a paths intersects the board edge or a track + */ + bool isValid( BOARD& aBoard, PCB_LAYER_ID aLayer, std::vector<BOARD_ITEM*> aBoardEdges, + std::vector<const BOARD_ITEM*> aIgnoreForTest, SHAPE_POLY_SET* aOutline, + std::pair<bool, bool> aTestLocalConcavity ) + { + if( !aOutline ) + return true; // We keep the segment if there is a problem + + if( !SegmentIntersectsBoard( a1, a2, aBoardEdges, aIgnoreForTest ) ) + return false; + + // The mid point should be inside the board. + // Tolerance of 100nm. + + VECTOR2I midPoint = ( a1 + a2 ) / 2; + int tolerance = 100; + + if( !( aOutline->Contains( midPoint, -1, tolerance ) + || aOutline->PointOnEdge( midPoint, tolerance ) ) ) + return false; + + if( false && ( aTestLocalConcavity.first || aTestLocalConcavity.second ) ) + { + // Test for local concavity. If it is localy convex, then it will not be a path of interest. + + double extendLine = 1000; // extend line by 1000nm + // In some cases, the projected point could be on the board edge + // In such cases, we wan to keep the line. + // We inflate the polygon to get a small margin for computation/rounding error. + // We might keep some unnecessary lines, but it's better than loosing the important ones. + double extendPoly = 100; // extend polygon by 10 nm + + VECTOR2D a( double( a1.x ), double( a1.y ) ); + VECTOR2D b( double( a2.x ), double( a2.y ) ); + + VECTOR2D dir( b - a ); + dir = dir * ( extendLine / dir.SquaredEuclideanNorm() ); + + SHAPE_POLY_SET outline2 = *aOutline; + outline2.Inflate( extendPoly, CORNER_STRATEGY::ROUND_ALL_CORNERS, 3 ); + + if( aTestLocalConcavity.first && !aOutline->Contains( a - dir, -1, 0 ) ) + return false; + + if( aTestLocalConcavity.second && !aOutline->Contains( b + dir, -1, 0 ) ) + return false; + } + + SEG segPath( a1, a2 ); + + if ( aLayer != Edge_Cuts ) + { + for( PCB_TRACK* track : aBoard.Tracks() ) + { + if( !track ) + continue; + + if( track->Type() == KICAD_T::PCB_TRACE_T && track->IsOnLayer( aLayer ) ) + { + std::shared_ptr<SHAPE> sh = track->GetEffectiveShape(); + + if( sh && sh->Type() == SHAPE_TYPE::SH_SEGMENT ) + { + SEG segTrack( track->GetStart(), track->GetEnd() ); + + if( segPath.Intersects( segTrack ) ) + return false; + } + } + } + } + return true; + } +}; + + +/* Check if q is inside a rectangle with diagonal [pr] */ +bool on_segment( VECTOR2I p, VECTOR2I q, VECTOR2I r ) +{ + return ( q.x <= ( p.x > r.x ? p.x : r.x ) && q.x >= ( p.x < r.x ? p.x : r.x ) + && q.y <= ( p.y > r.y ? p.y : r.y ) && q.y >= ( p.y < r.y ? p.y : r.y ) ); +} + +/* Find the orientation of a triplet ( p, q, r ) + 0 -> colinear + 1 -> Clockwise + 2 -> Counterclockwie + */ +int orientation( VECTOR2D p, VECTOR2D q, VECTOR2D r ) +{ + float val = ( q.y - p.y ) * ( r.x - q.x ) - ( q.x - p.x ) * ( r.y - q.y ); + if( val == 0 ) + return 0; + if( val > 0 ) + return 1; + return 2; +} + +//Check if line segments 'p1q1' and 'p2q2' intersect, excluding endpoint overlap + +bool segments_intersect( VECTOR2I p1, VECTOR2I q1, VECTOR2I p2, VECTOR2I q2 ) +{ + VECTOR2D a1( double( p1.x ), double( p1.y ) ); + VECTOR2D b1( double( q1.x ), double( q1.y ) ); + VECTOR2D a2( double( p2.x ), double( p2.y ) ); + VECTOR2D b2( double( q2.x ), double( q2.y ) ); + + int o1 = orientation( a1, b1, a2 ); + int o2 = orientation( a1, b1, b2 ); + int o3 = orientation( a2, b2, a1 ); + int o4 = orientation( a2, b2, b1 ); + + if( p1 == p2 || p1 == q2 || q1 == p2 || q1 == q2 ) + return false; + + if( o1 != o2 && o3 != o4 ) + return true; + + if( o1 == 0 && on_segment( p1, p2, q1 ) && p2 != p1 && p2 != q1 ) + return true; + + if( o2 == 0 && on_segment( p1, q2, q1 ) && q2 != p1 && q2 != q1 ) + return true; + + if( o3 == 0 && on_segment( p2, p1, q2 ) && p1 != p2 && p1 != q2 ) + return true; + + if( o4 == 0 && on_segment( p2, q1, q2 ) && q1 != p2 && q1 != q2 ) + return true; + + return false; +} + +class GraphConnection; +class GraphNode; +class CreepageGraph; +class CREEP_SHAPE; +class BE_SHAPE; +class BE_SHAPE_POINT; +class BE_SHAPE_ARC; +class BE_SHAPE_CIRCLE; +class CU_SHAPE; +class CU_SHAPE_SEGMENT; +class CU_SHAPE_CIRCLE; +class CU_SHAPE_ARC; + +/** @class CREEP_SHAPE + * + * @brief A class used to represent the shapes for creepage calculation + */ +class CREEP_SHAPE +{ +public: + enum class TYPE + { + UNDEFINED = 0, + POINT, + CIRCLE, + ARC + }; + CREEP_SHAPE() {}; + + + virtual int GetRadius() const { return 0; }; + virtual EDA_ANGLE GetStartAngle() const { return EDA_ANGLE( 0 ); }; + virtual EDA_ANGLE GetEndAngle() const { return EDA_ANGLE( 0 ); }; + virtual VECTOR2I GetStartPoint() const { return VECTOR2I( 0, 0 ); }; + virtual VECTOR2I GetEndPoint() const { return VECTOR2I( 0, 0 ); }; + VECTOR2I GetPos() const { return m_pos; }; + CREEP_SHAPE::TYPE GetType() const { return m_type; }; + const BOARD_ITEM* GetParent() const { return m_parent; }; + void SetParent( BOARD_ITEM* aParent ) { m_parent = aParent; }; + + virtual void ConnectChildren( GraphNode* a1, GraphNode* a2, CreepageGraph& aG ) const; + + std::vector<path_connection> ReversePaths( const std::vector<path_connection>& aV ) const + { + std::vector<path_connection> r; + r.reserve( aV.size() ); + + for( const auto& pc : aV ) + { + r.emplace_back( pc ); + std::swap( r.back().a1, r.back().a2 ); + } + + return r; + } + + std::vector<path_connection> Paths( const CREEP_SHAPE& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const + { + std::vector<path_connection> a; + return a; + }; + virtual std::vector<path_connection> Paths( const BE_SHAPE_POINT& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const + { + std::vector<path_connection> a; + return a; + }; + virtual std::vector<path_connection> Paths( const BE_SHAPE_CIRCLE& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const + { + std::vector<path_connection> a; + return a; + }; + virtual std::vector<path_connection> Paths( const BE_SHAPE_ARC& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const + { + std::vector<path_connection> a; + return a; + }; + virtual std::vector<path_connection> Paths( const CU_SHAPE_SEGMENT& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const + { + std::vector<path_connection> a; + return a; + }; + virtual std::vector<path_connection> Paths( const CU_SHAPE_CIRCLE& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const + { + std::vector<path_connection> a; + return a; + }; + virtual std::vector<path_connection> Paths( const CU_SHAPE_ARC& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const + { + std::vector<path_connection> a; + return a; + }; + + //virtual std::vector<path_connection> GetPathsCuToBe( CREEP_SHAPE* aShape ) const{ std::vector<path_connection> a; return a;}; + bool IsConductive() { return m_conductive; }; + + +protected: + bool m_conductive = false; + BOARD_ITEM* m_parent = nullptr; + VECTOR2I m_pos = VECTOR2I( 0, 0 ); + CREEP_SHAPE::TYPE m_type = CREEP_SHAPE::TYPE::UNDEFINED; +}; + + +/** @class CU_SHAPE + * + * @brief Creepage: a conductive shape + */ +class CU_SHAPE : public CREEP_SHAPE +{ +public: + CU_SHAPE() : CREEP_SHAPE() { m_conductive = true; }; +}; + +/** @class BE_SHAPE + * + * @brief Creepage: a board edge shape + */ +class BE_SHAPE : public CREEP_SHAPE +{ +public: + BE_SHAPE() : CREEP_SHAPE() { m_conductive = false; }; +}; + +/** @class CU_SHAPE_SEGMENT + * + * @brief Creepage: a conductive segment + */ +class CU_SHAPE_SEGMENT : public CU_SHAPE +{ +public: + CU_SHAPE_SEGMENT( VECTOR2I aStart, VECTOR2I aEnd, double aWidth = 0 ) : CU_SHAPE() + { + m_start = aStart; + m_end = aEnd; + m_width = aWidth; + } + + VECTOR2I GetStart() const { return m_start; }; + VECTOR2I GetEnd() const { return m_end; }; + double GetWidth() const { return m_width; }; + + std::vector<path_connection> Paths( const BE_SHAPE_POINT& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const override; + std::vector<path_connection> Paths( const BE_SHAPE_CIRCLE& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const override; + std::vector<path_connection> Paths( const BE_SHAPE_ARC& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const override; + std::vector<path_connection> Paths( const CU_SHAPE_SEGMENT& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const override; + std::vector<path_connection> Paths( const CU_SHAPE_CIRCLE& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const override; + std::vector<path_connection> Paths( const CU_SHAPE_ARC& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const override; + + +private: + VECTOR2I m_start = VECTOR2I( 0, 0 ); + VECTOR2I m_end = VECTOR2I( 0, 0 ); + double m_width = 0; +}; + +/** @class CU_SHAPE_CIRCLE + * + * @brief Creepage: a conductive circle + */ +class CU_SHAPE_CIRCLE : public CU_SHAPE +{ +public: + CU_SHAPE_CIRCLE( VECTOR2I aPos, double aRadius = 0 ) : CU_SHAPE() + { + m_pos = aPos; + m_radius = aRadius; + } + + VECTOR2I GetPos() const { return m_pos; }; + int GetRadius() const override { return m_radius; }; + + std::vector<path_connection> Paths( const BE_SHAPE_POINT& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const override; + std::vector<path_connection> Paths( const BE_SHAPE_CIRCLE& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const override; + std::vector<path_connection> Paths( const BE_SHAPE_ARC& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const override; + std::vector<path_connection> Paths( const CU_SHAPE_SEGMENT& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const override + { + return ReversePaths( aS2.Paths( *this, aMaxWeight, aMaxSquaredWeight ) ); + }; + + std::vector<path_connection> Paths( const CU_SHAPE_CIRCLE& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const override; + std::vector<path_connection> Paths( const CU_SHAPE_ARC& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const override; + +private: + VECTOR2I m_pos = VECTOR2I( 0, 0 ); + double m_radius = 1; +}; + +/** @class CU_SHAPE_ARC + * + * @brief Creepage: a conductive arc + */ +class CU_SHAPE_ARC : public CU_SHAPE_CIRCLE +{ +public: + CU_SHAPE_ARC( VECTOR2I aPos, double aRadius, EDA_ANGLE aStartAngle, EDA_ANGLE aEndAngle, + VECTOR2D aStartPoint, VECTOR2D aEndPoint ) : CU_SHAPE_CIRCLE( aPos, aRadius ) + { + m_pos = aPos; + m_type = CREEP_SHAPE::TYPE::ARC; + m_startAngle = aStartAngle; + m_endAngle = aEndAngle; + m_startPoint = aStartPoint; + m_endPoint = aEndPoint; + m_radius = aRadius; + } + + std::vector<path_connection> Paths( const BE_SHAPE_POINT& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const override; + std::vector<path_connection> Paths( const BE_SHAPE_CIRCLE& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const override; + std::vector<path_connection> Paths( const BE_SHAPE_ARC& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const override; + std::vector<path_connection> Paths( const CU_SHAPE_SEGMENT& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const override + { + return ReversePaths( aS2.Paths( *this, aMaxWeight, aMaxSquaredWeight ) ); + }; + std::vector<path_connection> Paths( const CU_SHAPE_CIRCLE& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const override + { + return ReversePaths( aS2.Paths( *this, aMaxWeight, aMaxSquaredWeight ) ); + }; + std::vector<path_connection> Paths( const CU_SHAPE_ARC& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const override; + + + EDA_ANGLE GetStartAngle() const override { return m_startAngle; } + EDA_ANGLE GetEndAngle() const override { return m_endAngle; } + int GetRadius() const override { return m_radius; } + + + VECTOR2I GetStartPoint() const override { return m_startPoint; } + VECTOR2I GetEndPoint() const override { return m_endPoint; } + EDA_ANGLE AngleBetweenStartAndEnd( const VECTOR2I aPoint ) const + { + EDA_ANGLE angle( aPoint - m_pos ); + while( angle < GetStartAngle() ) + angle += ANGLE_360; + while( angle > GetEndAngle() + ANGLE_360 ) + angle -= ANGLE_360; + + return angle; + } + double GetWidth() const { return m_width; }; + void SetWidth( double aW ) { m_width = aW; }; + +private: + int m_width = 0; + VECTOR2I m_pos = VECTOR2I( 0, 0 ); + double m_radius = 1; + EDA_ANGLE m_startAngle = EDA_ANGLE( 0 ); + EDA_ANGLE m_endAngle = EDA_ANGLE( 180 ); + VECTOR2I m_startPoint = VECTOR2I( 1, 0 ); + VECTOR2I m_endPoint = VECTOR2I( -1, 0 ); +}; + +/** @class Graphnode + * + * @brief a node in a @class CreepageGraph + */ +class GraphNode +{ +public: + enum TYPE + { + + POINT = 0, + CIRCLE, + ARC, + SEGMENT, + VIRTUAL + }; + + GraphNode( GraphNode::TYPE aType, CREEP_SHAPE* aParent, VECTOR2I aPos = VECTOR2I() ) + { + m_parent = aParent; + m_pos = aPos; + m_type = aType; + m_connectDirectly = true; + m_connections = {}; + }; + + ~GraphNode() {}; + + + CREEP_SHAPE* m_parent = nullptr; + std::vector<GraphConnection*> m_connections = {}; + VECTOR2I m_pos = VECTOR2I( 0, 0 ); + // Virtual nodes are connected with a 0 weight connection to equivalent net ( same net or netclass ) + bool m_virtual = false; + bool m_connectDirectly = true; + int m_net = -1; + + GraphNode::TYPE m_type; +}; + +/** @class GraphConnection + * + * @brief a connection in a @class CreepageGraph + */ +class GraphConnection +{ +public: + GraphConnection( GraphNode* aN1, GraphNode* aN2, path_connection aPc ) : n1( aN1 ), n2( aN2 ) + { + m_path = aPc; + }; + + GraphNode* n1 = nullptr; + GraphNode* n2 = nullptr; + path_connection m_path; + + std::vector<PCB_SHAPE> GetShapes(); + bool forceStraightLigne = false; +}; + + +bool compareShapes( const CREEP_SHAPE* a, const CREEP_SHAPE* b ) +{ + if( a->GetType() != b->GetType() ) + { + return a->GetType() < b->GetType(); + } + if( a->GetType() == CREEP_SHAPE::TYPE::POINT || a->GetType() == CREEP_SHAPE::TYPE::CIRCLE ) + { + auto posA = a->GetPos(); + auto posB = b->GetPos(); + + if( posA != posB ) + { + return posA < posB; + } + if( a->GetType() == CREEP_SHAPE::TYPE::CIRCLE ) + { + return a->GetRadius() < b->GetRadius(); + } + } + return false; +} + +bool areEquivalent( const CREEP_SHAPE* a, const CREEP_SHAPE* b ) +{ + if( a->GetType() != b->GetType() ) + { + return false; + } + if( a->GetType() == CREEP_SHAPE::TYPE::POINT ) + { + return a->GetPos() == b->GetPos(); + } + if( a->GetType() == CREEP_SHAPE::TYPE::CIRCLE ) + { + return a->GetPos() == b->GetPos() && ( a->GetRadius() == b->GetRadius() ); + } + return false; +} + + +/** @class BE_SHAPE_POINT + * + * @brief Creepage: a board edge point + */ +class BE_SHAPE_POINT : public BE_SHAPE +{ +public: + BE_SHAPE_POINT( VECTOR2I aPos ) : BE_SHAPE() + { + m_pos = aPos; + m_type = CREEP_SHAPE::TYPE::POINT; + } + + std::vector<path_connection> Paths( const BE_SHAPE_POINT& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const override; + std::vector<path_connection> Paths( const BE_SHAPE_CIRCLE& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const override; + std::vector<path_connection> Paths( const BE_SHAPE_ARC& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const override; + std::vector<path_connection> Paths( const CU_SHAPE_SEGMENT& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const override + { + return ReversePaths( aS2.Paths( *this, aMaxWeight, aMaxSquaredWeight ) ); + }; + std::vector<path_connection> Paths( const CU_SHAPE_CIRCLE& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const override + { + return ReversePaths( aS2.Paths( *this, aMaxWeight, aMaxSquaredWeight ) ); + } + std::vector<path_connection> Paths( const CU_SHAPE_ARC& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const override + { + return ReversePaths( aS2.Paths( *this, aMaxWeight, aMaxSquaredWeight ) ); + }; + + + void ConnectChildren( GraphNode* a1, GraphNode* a2, CreepageGraph& aG ) const override; +}; + +/** @class BE_SHAPE_CIRCLE + * + * @brief Creepage: a board edge circle + */ +class BE_SHAPE_CIRCLE : public BE_SHAPE +{ +public: + BE_SHAPE_CIRCLE( VECTOR2I aPos = VECTOR2I( 0, 0 ), int aRadius = 0 ) : BE_SHAPE() + { + m_pos = aPos; + m_radius = aRadius; + m_type = CREEP_SHAPE::TYPE::CIRCLE; + } + + std::vector<path_connection> Paths( const BE_SHAPE_POINT& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const override + { + return ReversePaths( aS2.Paths( *this, aMaxWeight, aMaxSquaredWeight ) ); + }; + std::vector<path_connection> Paths( const BE_SHAPE_CIRCLE& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const override; + std::vector<path_connection> Paths( const BE_SHAPE_ARC& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const override; + std::vector<path_connection> Paths( const CU_SHAPE_SEGMENT& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const override + { + return ReversePaths( aS2.Paths( *this, aMaxWeight, aMaxSquaredWeight ) ); + }; + std::vector<path_connection> Paths( const CU_SHAPE_CIRCLE& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const override + { + return ReversePaths( aS2.Paths( *this, aMaxWeight, aMaxSquaredWeight ) ); + }; + + + int GetRadius() const override { return m_radius; } + void ConnectChildren( GraphNode* a1, GraphNode* a2, CreepageGraph& aG ) const override; + void ShortenChildDueToGV( GraphNode* a1, GraphNode* a2, CreepageGraph& aG, + double aNormalWeight ) const; + + +protected: + int m_radius; +}; + +/** @class BE_SHAPE_ARC + * + * @brief Creepage: a board edge arc + */ +class BE_SHAPE_ARC : public BE_SHAPE_CIRCLE +{ +public: + BE_SHAPE_ARC( VECTOR2I aPos, int aRadius, EDA_ANGLE aStartAngle, EDA_ANGLE aEndAngle, + VECTOR2D aStartPoint, VECTOR2D aEndPoint ) : BE_SHAPE_CIRCLE( aPos, aRadius ) + { + m_type = CREEP_SHAPE::TYPE::ARC; + m_startAngle = aStartAngle; + m_endAngle = aEndAngle; + m_startPoint = aStartPoint; + m_endPoint = aEndPoint; + m_radius = aRadius; + } + + void ConnectChildren( GraphNode* a1, GraphNode* a2, CreepageGraph& aG ) const override; + + + std::vector<path_connection> Paths( const BE_SHAPE_POINT& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const override + { + return ReversePaths( aS2.Paths( *this, aMaxWeight, aMaxSquaredWeight ) ); + }; + std::vector<path_connection> Paths( const BE_SHAPE_CIRCLE& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const override + { + return ReversePaths( aS2.Paths( *this, aMaxWeight, aMaxSquaredWeight ) ); + }; + std::vector<path_connection> Paths( const BE_SHAPE_ARC& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const override; + + std::vector<path_connection> Paths( const CU_SHAPE_SEGMENT& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const override + { + return ReversePaths( aS2.Paths( *this, aMaxWeight, aMaxSquaredWeight ) ); + }; + std::vector<path_connection> Paths( const CU_SHAPE_CIRCLE& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const override + { + return ReversePaths( aS2.Paths( *this, aMaxWeight, aMaxSquaredWeight ) ); + }; + std::vector<path_connection> Paths( const CU_SHAPE_ARC& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const override + { + return ReversePaths( aS2.Paths( *this, aMaxWeight, aMaxSquaredWeight ) ); + }; + + + EDA_ANGLE GetStartAngle() const override { return m_startAngle; } + EDA_ANGLE GetEndAngle() const override { return m_endAngle; } + int GetRadius() const override { return m_radius; } + + + VECTOR2I GetStartPoint() const override { return m_startPoint; } + VECTOR2I GetEndPoint() const override { return m_endPoint; } + EDA_ANGLE AngleBetweenStartAndEnd( const VECTOR2I aPoint ) const + { + EDA_ANGLE angle( aPoint - m_pos ); + + while( angle < m_startAngle ) + angle += ANGLE_360; + while( angle > m_endAngle + ANGLE_360 ) + angle -= ANGLE_360; + + return angle; + } + + bool m_lastStartIsTangent; + bool m_lastEndIsTangent; + + std::pair<bool, bool> IsThereATangentPassingThroughPoint( const BE_SHAPE_POINT aPoint ) const; + +protected: + int m_radius; + EDA_ANGLE m_startAngle; + EDA_ANGLE m_endAngle; + VECTOR2I m_startPoint; + VECTOR2I m_endPoint; +}; + + +std::vector<path_connection> BE_SHAPE_POINT::Paths( const BE_SHAPE_POINT& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const +{ + std::vector<path_connection> result; + + double weight = ( this->GetPos() - aS2.GetPos() ).SquaredEuclideanNorm(); + + if( weight > aMaxSquaredWeight ) + return result; + + path_connection pc; + pc.a1 = this->GetPos(); + pc.a2 = aS2.GetPos(); + pc.weight = sqrt( weight ); + + result.push_back( pc ); + return result; +} + +std::vector<path_connection> BE_SHAPE_POINT::Paths( const BE_SHAPE_CIRCLE& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const +{ + std::vector<path_connection> result; + int radius = aS2.GetRadius(); + VECTOR2I pointPos = this->GetPos(); + VECTOR2I circleCenter = aS2.GetPos(); + + if( radius <= 0 ) + { + return result; + } + + double pointToCenterDistanceSquared = ( pointPos - circleCenter ).SquaredEuclideanNorm(); + double weightSquared = pointToCenterDistanceSquared - radius * radius; + + if( weightSquared > aMaxSquaredWeight ) + return result; + + + VECTOR2D direction1 = VECTOR2D( pointPos.x - circleCenter.x, pointPos.y - circleCenter.y ); + direction1 = direction1.Resize( 1 ); + + VECTOR2D direction2 = direction1.Perpendicular(); + + double radiusSquared = double( radius ) * double( radius ); + + double distance = sqrt( pointToCenterDistanceSquared ); + double value1 = radiusSquared / distance; + double value2 = sqrt( radiusSquared - value1 * value1 ); + + VECTOR2D resultPoint; + + path_connection pc; + pc.a1 = pointPos; + pc.weight = sqrt( weightSquared ); + + resultPoint = direction1 * value1 + direction2 * value2 + circleCenter; + pc.a2.x = int( resultPoint.x ); + pc.a2.y = int( resultPoint.y ); + result.push_back( pc ); + + resultPoint = direction1 * value1 - direction2 * value2 + circleCenter; + pc.a2.x = int( resultPoint.x ); + pc.a2.y = int( resultPoint.y ); + result.push_back( pc ); + + return result; +} + +std::pair<bool, bool> +BE_SHAPE_ARC::IsThereATangentPassingThroughPoint( const BE_SHAPE_POINT aPoint ) const +{ + std::pair<bool, bool> result; + double R = m_radius; + + VECTOR2I newPoint = aPoint.GetPos() - m_pos; + + if( newPoint.SquaredEuclideanNorm() <= R * R ) + { + // If the point is inside the arc + result.first = false; + result.second = false; + return result; + } + + EDA_ANGLE testAngle = AngleBetweenStartAndEnd( aPoint.GetPos() ); + + double startAngle = m_startAngle.AsRadians(); + double endAngle = m_endAngle.AsRadians(); + double pointAngle = testAngle.AsRadians(); + + bool greaterThan180 = ( m_endAngle - m_startAngle ) > EDA_ANGLE( 180 ); + bool connectToEndPoint; + + connectToEndPoint = ( cos( startAngle ) * newPoint.x + sin( startAngle ) * newPoint.y >= R ); + + if( greaterThan180 ) + connectToEndPoint &= ( cos( endAngle ) * newPoint.x + sin( endAngle ) * newPoint.y <= R ); + + connectToEndPoint |= ( cos( endAngle ) * newPoint.x + sin( endAngle ) * newPoint.y <= R ) + && ( pointAngle >= endAngle || pointAngle <= startAngle ); + + + result.first = !connectToEndPoint; + + connectToEndPoint = ( cos( endAngle ) * newPoint.x + sin( endAngle ) * newPoint.y >= R ); + + if( greaterThan180 ) + connectToEndPoint &= + ( cos( startAngle ) * newPoint.x + sin( startAngle ) * newPoint.y <= R ); + + connectToEndPoint |= ( cos( startAngle ) * newPoint.x + sin( startAngle ) * newPoint.y <= R ) + && ( pointAngle >= endAngle || pointAngle <= startAngle ); + + + result.second = !connectToEndPoint; + return result; +} + +std::vector<path_connection> BE_SHAPE_POINT::Paths( const BE_SHAPE_ARC& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const +{ + std::vector<path_connection> result; + VECTOR2I center = aS2.GetPos(); + double radius = aS2.GetRadius(); + + // First path tries to connect to start point + // Second path tries to connect to end point + std::pair<bool, bool> behavesLikeCircle; + behavesLikeCircle = aS2.IsThereATangentPassingThroughPoint( *this ); + + if( behavesLikeCircle.first && behavesLikeCircle.second ) + { + BE_SHAPE_CIRCLE csc( center, radius ); + return this->Paths( csc, aMaxWeight, aMaxSquaredWeight ); + } + + if( behavesLikeCircle.first ) + { + BE_SHAPE_CIRCLE csc( center, radius ); + std::vector<path_connection> paths = this->Paths( csc, aMaxWeight, aMaxSquaredWeight ); + + if( paths.size() > 1 ) // Point to circle creates either 0 or 2 connections + { + result.push_back( paths[1] ); + } + } + else + { + BE_SHAPE_POINT csp1( aS2.GetStartPoint() ); + + for( path_connection pc : this->Paths( csp1, aMaxWeight, aMaxSquaredWeight ) ) + { + result.push_back( pc ); + } + } + if( behavesLikeCircle.second ) + { + BE_SHAPE_CIRCLE csc( center, radius ); + std::vector<path_connection> paths = this->Paths( csc, aMaxWeight, aMaxSquaredWeight ); + + if( paths.size() > 1 ) // Point to circle creates either 0 or 2 connections + { + result.push_back( paths[0] ); + } + } + else + { + BE_SHAPE_POINT csp1( aS2.GetEndPoint() ); + + for( path_connection pc : this->Paths( csp1, aMaxWeight, aMaxSquaredWeight ) ) + { + result.push_back( pc ); + } + } + return result; +} + +std::vector<path_connection> BE_SHAPE_CIRCLE::Paths( const BE_SHAPE_ARC& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const +{ + std::vector<path_connection> result; + VECTOR2I circleCenter = this->GetPos(); + double circleRadius = this->GetRadius(); + VECTOR2I arcCenter = aS2.GetPos(); + double arcRadius = aS2.GetRadius(); + EDA_ANGLE arcStartAngle = aS2.GetStartAngle(); + EDA_ANGLE arcEndAngle = aS2.GetEndAngle(); + + double centerDistance = ( circleCenter - arcCenter ).EuclideanNorm(); + + if( centerDistance + arcRadius < circleRadius ) + { + // The arc is inside the circle + return result; + } + + BE_SHAPE_POINT csp1( aS2.GetStartPoint() ); + BE_SHAPE_POINT csp2( aS2.GetEndPoint() ); + BE_SHAPE_CIRCLE csc( arcCenter, arcRadius ); + + + for( path_connection pc : this->Paths( csc, aMaxWeight, aMaxSquaredWeight ) ) + { + EDA_ANGLE pointAngle = aS2.AngleBetweenStartAndEnd( pc.a2 - arcCenter ); + + if( pointAngle <= aS2.GetEndAngle() ) + result.push_back( pc ); + } + + if( result.size() == 4 ) + { + // It behaved as a circle + return result; + } + + for( BE_SHAPE_POINT csp : { csp1, csp2 } ) + { + for( path_connection pc : this->Paths( csp, aMaxWeight, aMaxSquaredWeight ) ) + { + if( !segmentIntersectsArc( pc.a1, pc.a2, arcCenter, arcRadius, + arcStartAngle.AsDegrees(), arcEndAngle.AsDegrees() ) ) + result.push_back( pc ); + } + } + + + return result; +} + + +std::vector<path_connection> BE_SHAPE_ARC::Paths( const BE_SHAPE_ARC& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const +{ + std::vector<path_connection> result; + VECTOR2I circleCenter = this->GetPos(); + double circleRadius = this->GetRadius(); + VECTOR2I arcCenter = aS2.GetPos(); + double arcRadius = aS2.GetRadius(); + + double centerDistance = ( circleCenter - arcCenter ).EuclideanNorm(); + + if( centerDistance + arcRadius < circleRadius ) + { + // The arc is inside the circle + return result; + } + + BE_SHAPE_POINT csp1( aS2.GetStartPoint() ); + BE_SHAPE_POINT csp2( aS2.GetEndPoint() ); + BE_SHAPE_CIRCLE csc( arcCenter, arcRadius ); + + + for( path_connection pc : this->Paths( BE_SHAPE_CIRCLE( aS2.GetPos(), aS2.GetRadius() ), + aMaxWeight, aMaxSquaredWeight ) ) + { + EDA_ANGLE pointAngle = aS2.AngleBetweenStartAndEnd( pc.a2 - arcCenter ); + + if( pointAngle <= aS2.GetEndAngle() ) + result.push_back( pc ); + } + + for( path_connection pc : BE_SHAPE_CIRCLE( this->GetPos(), this->GetRadius() ) + .Paths( aS2, aMaxWeight, aMaxSquaredWeight ) ) + { + EDA_ANGLE pointAngle = this->AngleBetweenStartAndEnd( pc.a2 - arcCenter ); + + if( pointAngle <= this->GetEndAngle() ) + result.push_back( pc ); + } + + return result; +} + + +std::vector<path_connection> BE_SHAPE_CIRCLE::Paths( const BE_SHAPE_CIRCLE& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const +{ + std::vector<path_connection> result; + + VECTOR2I p1 = this->GetPos(); + VECTOR2I p2 = aS2.GetPos(); + + VECTOR2D distSquared( double( ( p2 - p1 ).x ), double( ( p2 - p1 ).y ) ); + double weightSquared = distSquared.SquaredEuclideanNorm(); + + double R1 = this->GetRadius(); + double R2 = aS2.GetRadius(); + + double Rdiff = abs( R1 - R2 ); + double Rsum = R1 + R2; + + // "Straight" paths + double weightSquared1 = weightSquared - Rdiff * Rdiff; + // "Crossed" paths + double weightSquared2 = weightSquared - Rsum * Rsum; + + if( weightSquared1 <= aMaxSquaredWeight ) + { + VECTOR2D direction1 = VECTOR2D( p2.x - p1.x, p2.y - p1.y ); + direction1 = direction1.Resize( 1 ); + VECTOR2D direction2 = direction1.Perpendicular(); + + double D = sqrt( weightSquared ); + double ratio1 = ( R1 - R2 ) / D; + double ratio2 = sqrt( 1 - ratio1 * ratio1 ); + + + path_connection pc; + pc.weight = sqrt( weightSquared1 ); + + VECTOR2D point; + pc.a1 = p1 + direction1 * R1 * ratio1 + direction2 * R1 * ratio2; + pc.a2 = p2 + direction1 * R2 * ratio1 + direction2 * R2 * ratio2; + + result.push_back( pc ); + + pc.a1 = p1 + direction1 * R1 * ratio1 - direction2 * R1 * ratio2; + pc.a2 = p2 + direction1 * R2 * ratio1 - direction2 * R2 * ratio2; + + result.push_back( pc ); + } + if( weightSquared2 <= aMaxSquaredWeight ) + { + VECTOR2D direction1 = VECTOR2D( p2.x - p1.x, p2.y - p1.y ); + direction1 = direction1.Resize( 1 ); + VECTOR2D direction2 = direction1.Perpendicular(); + + double D = sqrt( weightSquared ); + double ratio1 = ( R1 + R2 ) / D; + double ratio2 = sqrt( 1 - ratio1 * ratio1 ); + + + path_connection pc; + pc.weight = sqrt( weightSquared2 ); + + VECTOR2D point; + pc.a1 = p1 + direction1 * R1 * ratio1 + direction2 * R1 * ratio2; + pc.a2 = p2 - direction1 * R2 * ratio1 - direction2 * R2 * ratio2; + + result.push_back( pc ); + + pc.a1 = p1 + direction1 * R1 * ratio1 - direction2 * R1 * ratio2; + pc.a2 = p2 - direction1 * R2 * ratio1 + direction2 * R2 * ratio2; + + result.push_back( pc ); + } + + return result; +} + +/** @class CreepageGraph + * + * @brief A graph with nodes and connections for creepage calculation + */ +class CreepageGraph +{ +public: + CreepageGraph( BOARD& aBoard ) : m_board( aBoard ) {}; + ~CreepageGraph() + { + for( GraphConnection* gc : m_connections ) + { + if( gc ) + { + delete gc; + gc = nullptr; + } + } + for( GraphNode* gn : m_nodes ) + if( gn ) + { + delete gn; + gn = nullptr; + } + for( CREEP_SHAPE* cs : m_shapeCollection ) + if( cs ) + { + delete cs; + cs = nullptr; + } + }; + + BOARD& m_board; + std::vector<BOARD_ITEM*> m_boardEdge; + SHAPE_POLY_SET* m_boardOutline; + + std::vector<GraphNode*> m_nodes; + std::vector<GraphConnection*> m_connections; + + // Add a node to the graph. If an equivalent node exists, returns the pointer of the existing node instead + GraphNode* AddNode( GraphNode::TYPE aType, CREEP_SHAPE* aParent = nullptr, + VECTOR2I aPos = VECTOR2I() ); + GraphNode* AddNodeVirtual(); + GraphConnection* AddConnection( GraphNode* aN1, GraphNode* aN2, path_connection aPc ); + GraphConnection* AddConnection( GraphNode* aN1, GraphNode* aN2 ); + GraphNode* FindNode( GraphNode::TYPE aType, CREEP_SHAPE* aParent, VECTOR2I aPos ); + + void RemoveConnection( GraphConnection* ); + void Trim( double aWeightLimit ); + void Addshape( const SHAPE& aShape, GraphNode* aConnectTo = nullptr, + BOARD_ITEM* aParent = nullptr ); + + double Solve( GraphNode* aFrom, GraphNode* aTo, std::vector<GraphConnection*>& aResult ); + + void GeneratePaths( double aMaxWeight, PCB_LAYER_ID aLayer, bool aGenerateBoardEdges = true ); + GraphNode* AddNetElements( int aNetCode, PCB_LAYER_ID aLayer, int aMaxCreepage ); + + void SetTarget( double aTarget ); + double GetTarget() { return m_creepageTarget; }; + int m_minGrooveWidth = 0; + + std::vector<CREEP_SHAPE*> m_shapeCollection; + +private: + double m_creepageTarget; + double m_creepageTargetSquared; +}; + + +std::vector<PCB_SHAPE> GraphConnection::GetShapes() +{ + std::vector<PCB_SHAPE> shapes = std::vector<PCB_SHAPE>(); + int lineWidth = m_path.weight / 100; + + if( !m_path.m_show ) + return shapes; + + if( !n1 || !n2 ) + return shapes; + + if( n1->m_type == GraphNode::TYPE::VIRTUAL || n2->m_type == GraphNode::TYPE::VIRTUAL ) + { + return shapes; + } + + if( !forceStraightLigne && n1->m_parent && ( n1->m_parent == n2->m_parent ) + && ( n1->m_parent->GetType() == CREEP_SHAPE::TYPE::CIRCLE ) ) + { + VECTOR2I center = n1->m_parent->GetPos(); + VECTOR2I R1 = n1->m_pos - center; + VECTOR2I R2 = n2->m_pos - center; + PCB_SHAPE s( nullptr, SHAPE_T::ARC ); + + if( R1.Cross( R2 ) > 0 ) + { + s.SetStart( n1->m_pos ); + s.SetEnd( n2->m_pos ); + } + else + { + s.SetStart( n2->m_pos ); + s.SetEnd( n1->m_pos ); + } + s.SetCenter( center ); + + + s.SetWidth( lineWidth ); + s.SetLayer( Eco1_User ); + + shapes.push_back( s ); + return shapes; + } + + else if( !forceStraightLigne && n1->m_parent && ( n1->m_parent == n2->m_parent ) + && n1->m_parent->GetType() == CREEP_SHAPE::TYPE::ARC ) + { + BE_SHAPE_ARC* arc = dynamic_cast<BE_SHAPE_ARC*>( n1->m_parent ); + + VECTOR2I center = arc->GetPos(); + VECTOR2I R1 = n1->m_pos - center; + VECTOR2I R2 = n2->m_pos - center; + PCB_SHAPE s( nullptr, SHAPE_T::ARC ); + + + if( R1.Cross( R2 ) > 0 ) + { + s.SetStart( n1->m_pos ); + s.SetEnd( n2->m_pos ); + } + else + { + s.SetStart( n2->m_pos ); + s.SetEnd( n1->m_pos ); + } + + s.SetCenter( center ); + + //Check that we are on the correct side of the arc. + VECTOR2I mid = s.GetArcMid(); + EDA_ANGLE midAngle = arc->AngleBetweenStartAndEnd( mid ); + + if( midAngle > arc->GetEndAngle() ) + { + VECTOR2I tmp; + tmp = s.GetStart(); + s.SetStart( s.GetEnd() ); + s.SetEnd( tmp ); + s.SetCenter( center ); + } + + s.SetWidth( lineWidth ); + s.SetLayer( Eco1_User ); + + shapes.push_back( s ); + return shapes; + } + + PCB_SHAPE s; + s.SetStart( m_path.a1 ); + s.SetEnd( m_path.a2 ); + + s.SetWidth( lineWidth ); + + s.SetLayer( Eco1_User ); + + shapes.push_back( s ); + + return shapes; +} + +void CREEP_SHAPE::ConnectChildren( GraphNode* a1, GraphNode* a2, CreepageGraph& aG ) const +{ +} + + +void BE_SHAPE_POINT::ConnectChildren( GraphNode* a1, GraphNode* a2, CreepageGraph& aG ) const +{ +} + +void BE_SHAPE_CIRCLE::ShortenChildDueToGV( GraphNode* a1, GraphNode* a2, CreepageGraph& aG, + double aNormalWeight ) const +{ + EDA_ANGLE angle1 = EDA_ANGLE( a1->m_pos - m_pos ); + EDA_ANGLE angle2 = EDA_ANGLE( a2->m_pos - m_pos ); + + while( angle1 < 0 ) + angle1 += ANGLE_360; + while( angle2 < 0 ) + angle2 += ANGLE_360; + while( angle1 > ANGLE_360 ) + angle1 -= ANGLE_360; + while( angle2 > ANGLE_360 ) + angle2 -= ANGLE_360; + + + EDA_ANGLE maxAngle = angle1 > angle2 ? angle1 : angle2; + EDA_ANGLE skipAngle = + EDA_ANGLE( asin( float( aG.m_minGrooveWidth ) / ( 2 * m_radius ) ), RADIANS_T ); + skipAngle += skipAngle; // Cannot multiply EDA_ANGLE by scalar, but this really is angle *2 + EDA_ANGLE pointAngle = maxAngle - skipAngle; + + VECTOR2I skipPoint = m_pos; + skipPoint.x += m_radius * cos( pointAngle.AsRadians() ); + skipPoint.y += m_radius * sin( pointAngle.AsRadians() ); + + + GraphNode* gnt = aG.AddNode( GraphNode::POINT, a1->m_parent, skipPoint ); + + path_connection pc; + + pc.a1 = maxAngle == angle2 ? a1->m_pos : a2->m_pos; + pc.a2 = skipPoint; + pc.weight = aNormalWeight - aG.m_minGrooveWidth; + aG.AddConnection( maxAngle == angle2 ? a1 : a2, gnt, pc ); + + pc.a1 = skipPoint; + pc.a2 = maxAngle == angle2 ? a2->m_pos : a1->m_pos; + pc.weight = aG.m_minGrooveWidth; + + GraphConnection* gc = aG.AddConnection( gnt, maxAngle == angle2 ? a2 : a1, pc ); + + if( gc ) + gc->forceStraightLigne = true; + return; +} + +void BE_SHAPE_CIRCLE::ConnectChildren( GraphNode* a1, GraphNode* a2, CreepageGraph& aG ) const +{ + if( !a1 || !a2 ) + return; + + VECTOR2D distI( a1->m_pos - a2->m_pos ); + VECTOR2D distD( double( distI.x ), double( distI.y ) ); + double weight = distD.EuclideanNorm(); + + weight = m_radius * 2 * atan2( weight, 2.0 * m_radius ); + + if( ( weight > aG.GetTarget() ) ) + return; + + if( aG.m_minGrooveWidth <= 0 ) + { + path_connection pc; + pc.a1 = a1->m_pos; + pc.a2 = a2->m_pos; + pc.weight = weight; + + aG.AddConnection( a1, a2, pc ); + return; + } + + if( weight > aG.m_minGrooveWidth ) + { + ShortenChildDueToGV( a1, a2, aG, weight ); + } + // Else well.. this paths will be "shorted" by another one + return; +} + + +void BE_SHAPE_ARC::ConnectChildren( GraphNode* a1, GraphNode* a2, CreepageGraph& aG ) const +{ + if( !a1 || !a2 ) + return; + + EDA_ANGLE angle1 = AngleBetweenStartAndEnd( a1->m_pos ); + EDA_ANGLE angle2 = AngleBetweenStartAndEnd( a2->m_pos ); + + double weight = abs( m_radius * ( angle2 - angle1 ).AsRadians() ); + + if( aG.m_minGrooveWidth <= 0 ) + { + if( ( weight > aG.GetTarget() ) ) + return; + + path_connection pc; + pc.a1 = a1->m_pos; + pc.a2 = a2->m_pos; + pc.weight = weight; + + aG.AddConnection( a1, a2, pc ); + return; + } + + if( weight > aG.m_minGrooveWidth ) + { + ShortenChildDueToGV( a1, a2, aG, weight ); + } +} + +void CreepageGraph::SetTarget( double aTarget ) +{ + m_creepageTarget = aTarget; + m_creepageTargetSquared = aTarget * aTarget; +} + +bool segmentIntersectsArc( VECTOR2I p1, VECTOR2I p2, VECTOR2I center, double radius, + double startAngle, double endAngle ) +{ + // Vector from p1 to p2 + VECTOR2I D = p2 - p1; + // Vector from p1 to center + VECTOR2I F = p1 - center; + + // Convert to double precision + VECTOR2D d( D.x, D.y ); + VECTOR2D f( F.x, F.y ); + + // Coefficients for the quadratic equation + double a = d.x * d.x + d.y * d.y; + double b = 2 * ( f.x * d.x + f.y * d.y ); + double c = ( f.x * f.x + f.y * f.y ) - radius * radius; + + // Discriminant of the quadratic equation + double discriminant = b * b - 4 * a * c; + + if( discriminant < 0 ) + { + return false; // No intersection with the circle + } + else + { + discriminant = sqrt( discriminant ); + double t1 = ( -b - discriminant ) / ( 2 * a ); + double t2 = ( -b + discriminant ) / ( 2 * a ); + + for( double tn : { t1, t2 } ) + { + if( tn < 0 || tn > 1 ) + continue; + + VECTOR2D intersection( p1.x + tn * d.x, p1.y + tn * d.y ); + VECTOR2D toIntersection = + VECTOR2D( intersection.x - center.x, intersection.y - center.y ); + + EDA_ANGLE angle( toIntersection ); + + if( angle < startAngle ) + angle += ANGLE_360; + + if( angle <= endAngle ) + return true; + } + + return false; // No valid intersection with the arc + } +} + +std::vector<path_connection> CU_SHAPE_SEGMENT::Paths( const BE_SHAPE_POINT& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const +{ + std::vector<path_connection> result; + VECTOR2I start = this->GetStart(); + VECTOR2I end = this->GetEnd(); + double halfWidth = this->GetWidth() / 2; + EDA_ANGLE trackAngle( end - start ); + VECTOR2I pointPos = aS2.GetPos(); + + double length = ( start - end ).EuclideanNorm(); + double projectedPos = cos( trackAngle.AsRadians() ) * ( pointPos.x - start.x ) + + sin( trackAngle.AsRadians() ) * ( pointPos.y - start.y ); + + VECTOR2I newPoint; + + if( projectedPos <= 0 ) + { + newPoint = start + ( pointPos - start ).Resize( halfWidth ); + } + else if( projectedPos >= length ) + { + newPoint = end + ( pointPos - end ).Resize( halfWidth ); + } + else + { + double posOnSegment = ( start - pointPos ).SquaredEuclideanNorm() + - ( end - pointPos ).SquaredEuclideanNorm(); + posOnSegment = posOnSegment / ( 2 * length ) + length / 2; + + newPoint = start + ( end - start ).Resize( posOnSegment ); + newPoint += ( pointPos - newPoint ).Resize( halfWidth ); + } + + double weightSquared = ( pointPos - newPoint ).SquaredEuclideanNorm(); + + if( weightSquared > aMaxSquaredWeight ) + return result; + + path_connection pc; + pc.a1 = newPoint; + pc.a2 = pointPos; + pc.weight = sqrt( weightSquared ); + + result.push_back( pc ); + return result; +} + + +std::vector<path_connection> CU_SHAPE_SEGMENT::Paths( const BE_SHAPE_CIRCLE& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const +{ + std::vector<path_connection> result; + VECTOR2I start = this->GetStart(); + VECTOR2I end = this->GetEnd(); + double halfWidth = this->GetWidth() / 2; + + double circleRadius = aS2.GetRadius(); + VECTOR2I circleCenter = aS2.GetPos(); + double length = ( start - end ).EuclideanNorm(); + EDA_ANGLE trackAngle( end - start ); + + double weightSquared = std::numeric_limits<double>::infinity(); + VECTOR2I PointOnTrack, PointOnCircle; + + // There are two possible paths + // First the one on the side of the start of the track. + double projectedPos1 = cos( trackAngle.AsRadians() ) * ( circleCenter.x - start.x ) + + sin( trackAngle.AsRadians() ) * ( circleCenter.y - start.y ); + double projectedPos2 = projectedPos1 + circleRadius; + projectedPos1 = projectedPos1 - circleRadius; + + double trackSide = ( end - start ).Cross( circleCenter - start ) > 0 ? 1 : -1; + + if( ( projectedPos1 < 0 && projectedPos2 < 0 ) ) + { + CU_SHAPE_CIRCLE csc( start, halfWidth ); + for( path_connection pc : csc.Paths( aS2, aMaxWeight, aMaxSquaredWeight ) ) + { + result.push_back( pc ); + } + } + else if( ( projectedPos1 > length && projectedPos2 > length ) ) + { + CU_SHAPE_CIRCLE csc( end, halfWidth ); + for( path_connection pc : csc.Paths( aS2, aMaxWeight, aMaxSquaredWeight ) ) + { + result.push_back( pc ); + } + } + + else if( ( projectedPos1 >= 0 ) && ( projectedPos1 <= length ) && ( projectedPos2 >= 0 ) + && ( projectedPos2 <= length ) ) + { + // Both point connects to the segment part of the track + PointOnTrack = start; + PointOnTrack += ( end - start ).Resize( projectedPos1 ); + PointOnTrack += ( end - start ).Perpendicular().Resize( halfWidth ) * trackSide; + PointOnCircle = circleCenter - ( end - start ).Resize( circleRadius ); + weightSquared = ( PointOnCircle - PointOnTrack ).SquaredEuclideanNorm(); + + if( weightSquared < aMaxSquaredWeight ) + { + path_connection pc; + pc.a1 = PointOnTrack; + pc.a2 = PointOnCircle; + pc.weight = sqrt( weightSquared ); + + result.push_back( pc ); + + PointOnTrack = start; + PointOnTrack += ( end - start ).Resize( projectedPos2 ); + PointOnTrack += ( end - start ).Perpendicular().Resize( halfWidth ) * trackSide; + PointOnCircle = circleCenter + ( end - start ).Resize( circleRadius ); + + + pc.a1 = PointOnTrack; + pc.a2 = PointOnCircle; + + result.push_back( pc ); + } + } + else if( ( ( projectedPos1 >= 0 ) && ( projectedPos1 <= length ) ) + && ( ( projectedPos2 > length ) || projectedPos2 < 0 ) ) + { + CU_SHAPE_CIRCLE csc( end, halfWidth ); + std::vector<path_connection> pcs = csc.Paths( aS2, aMaxWeight, aMaxSquaredWeight ); + + if ( pcs.size() < 2 ) + return result; + + result.push_back( pcs.at( trackSide == 1 ? 1 : 0 ) ); + + + PointOnTrack = start; + PointOnTrack += ( end - start ).Resize( projectedPos1 ); + PointOnTrack += ( end - start ).Perpendicular().Resize( halfWidth ) * trackSide; + PointOnCircle = circleCenter - ( end - start ).Resize( circleRadius ); + weightSquared = ( PointOnCircle - PointOnTrack ).SquaredEuclideanNorm(); + + if( weightSquared < aMaxSquaredWeight ) + { + path_connection pc; + pc.a1 = PointOnTrack; + pc.a2 = PointOnCircle; + pc.weight = sqrt( weightSquared ); + + result.push_back( pc ); + } + } + else if( ( ( projectedPos2 >= 0 ) && ( projectedPos2 <= length ) ) + && ( ( projectedPos1 > length ) || projectedPos1 < 0 ) ) + { + CU_SHAPE_CIRCLE csc( start, halfWidth ); + std::vector<path_connection> pcs = csc.Paths( aS2, aMaxWeight, aMaxSquaredWeight ); + + if ( pcs.size() < 2 ) + return result; + + result.push_back( pcs.at( trackSide == 1 ? 0 : 1 ) ); + + PointOnTrack = start; + PointOnTrack += ( end - start ).Resize( projectedPos2 ); + PointOnTrack += ( end - start ).Perpendicular().Resize( halfWidth ) * trackSide; + PointOnCircle = circleCenter + ( end - start ).Resize( circleRadius ); + weightSquared = ( PointOnCircle - PointOnTrack ).SquaredEuclideanNorm(); + + if( weightSquared < aMaxSquaredWeight ) + { + path_connection pc; + pc.a1 = PointOnTrack; + pc.a2 = PointOnCircle; + pc.weight = sqrt( weightSquared ); + + result.push_back( pc ); + } + } + + return result; +} + + +std::vector<path_connection> CU_SHAPE_SEGMENT::Paths( const BE_SHAPE_ARC& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const +{ + std::vector<path_connection> result; + + BE_SHAPE_CIRCLE bsc( aS2.GetPos(), aS2.GetRadius() ); + + for( auto pc : this->Paths( bsc, aMaxWeight, aMaxSquaredWeight ) ) + { + EDA_ANGLE testAngle = aS2.AngleBetweenStartAndEnd( pc.a2 ); + + if( testAngle < aS2.GetEndAngle() ) + { + result.push_back( pc ); + } + } + + if( result.size() < 2 ) + { + BE_SHAPE_POINT bsp1( aS2.GetStartPoint() ); + BE_SHAPE_POINT bsp2( aS2.GetEndPoint() ); + + VECTOR2I beArcPos = aS2.GetPos(); + int beArcRadius = aS2.GetRadius(); + EDA_ANGLE beArcStartAngle = aS2.GetStartAngle(); + EDA_ANGLE beArcEndAngle = aS2.GetEndAngle(); + + for( auto pc : this->Paths( bsp1, aMaxWeight, aMaxSquaredWeight ) ) + if( !segmentIntersectsArc( pc.a1, pc.a2, beArcPos, beArcRadius, + beArcStartAngle.AsDegrees(), beArcEndAngle.AsDegrees() ) ) + result.push_back( pc ); + + for( auto pc : this->Paths( bsp2, aMaxWeight, aMaxSquaredWeight ) ) + if( !segmentIntersectsArc( pc.a1, pc.a2, beArcPos, beArcRadius, + beArcStartAngle.AsDegrees(), beArcEndAngle.AsDegrees() ) ) + result.push_back( pc ); + } + + return result; +} + + +std::vector<path_connection> CU_SHAPE_CIRCLE::Paths( const BE_SHAPE_ARC& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const +{ + std::vector<path_connection> result; + VECTOR2I beArcPos = aS2.GetPos(); + int beArcRadius = aS2.GetRadius(); + EDA_ANGLE beArcStartAngle = aS2.GetStartAngle(); + EDA_ANGLE beArcEndAngle = aS2.GetEndAngle(); + + BE_SHAPE_CIRCLE bsc( beArcPos, beArcRadius ); + + for( auto pc : this->Paths( bsc, aMaxWeight, aMaxSquaredWeight ) ) + { + EDA_ANGLE testAngle = aS2.AngleBetweenStartAndEnd( pc.a2 ); + + if( testAngle < aS2.GetEndAngle() ) + { + result.push_back( pc ); + } + } + + if( result.size() < 2 ) + { + BE_SHAPE_POINT bsp1( aS2.GetStartPoint() ); + BE_SHAPE_POINT bsp2( aS2.GetEndPoint() ); + + for( auto pc : this->Paths( bsp1, aMaxWeight, aMaxSquaredWeight ) ) + if( !segmentIntersectsArc( pc.a1, pc.a2, beArcPos, beArcRadius, + beArcStartAngle.AsDegrees(), beArcEndAngle.AsDegrees() ) ) + result.push_back( pc ); + + for( auto pc : this->Paths( bsp2, aMaxWeight, aMaxSquaredWeight ) ) + if( !segmentIntersectsArc( pc.a1, pc.a2, beArcPos, beArcRadius, + beArcStartAngle.AsDegrees(), beArcEndAngle.AsDegrees() ) ) + result.push_back( pc ); + } + return result; +} + +std::vector<path_connection> CU_SHAPE_ARC::Paths( const BE_SHAPE_CIRCLE& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const +{ + std::vector<path_connection> result; + + CU_SHAPE_CIRCLE csc( this->GetPos(), this->GetRadius() + this->GetWidth() / 2 ); + + for( auto pc : this->Paths( csc, aMaxWeight, aMaxSquaredWeight ) ) + { + EDA_ANGLE testAngle = this->AngleBetweenStartAndEnd( pc.a2 ); + + if( testAngle < this->GetEndAngle() ) + { + result.push_back( pc ); + } + } + + if ( result.size() < 2 ) + { + CU_SHAPE_CIRCLE csc1( this->GetStartPoint(), this->GetWidth() / 2 ); + CU_SHAPE_CIRCLE csc2( this->GetEndPoint(), this->GetWidth() / 2 ); + + for ( auto pc : this->Paths( csc1, aMaxWeight, aMaxSquaredWeight) ) + result.push_back( pc ); + + for ( auto pc : this->Paths( csc2, aMaxWeight, aMaxSquaredWeight) ) + result.push_back( pc ); + } + + return result; +} + + +std::vector<path_connection> CU_SHAPE_ARC::Paths( const BE_SHAPE_ARC& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const +{ + std::vector<path_connection> result; + VECTOR2I beArcPos = aS2.GetPos(); + int beArcRadius = aS2.GetRadius(); + EDA_ANGLE beArcStartAngle = aS2.GetStartAngle(); + EDA_ANGLE beArcEndAngle = aS2.GetEndAngle(); + + BE_SHAPE_CIRCLE bsc( aS2.GetPos(), aS2.GetRadius() ); + + for( auto pc : this->Paths( bsc, aMaxWeight, aMaxSquaredWeight ) ) + { + EDA_ANGLE testAngle = aS2.AngleBetweenStartAndEnd( pc.a2 ); + + if( testAngle < aS2.GetEndAngle() ) + { + result.push_back( pc ); + } + } + + if( result.size() < 2 ) + { + BE_SHAPE_POINT bsp1( aS2.GetStartPoint() ); + BE_SHAPE_POINT bsp2( aS2.GetEndPoint() ); + + for( auto pc : this->Paths( bsp1, aMaxWeight, aMaxSquaredWeight ) ) + if( !segmentIntersectsArc( pc.a1, pc.a2, beArcPos, beArcRadius, + beArcStartAngle.AsDegrees(), beArcEndAngle.AsDegrees() ) ) + result.push_back( pc ); + + for( auto pc : this->Paths( bsp2, aMaxWeight, aMaxSquaredWeight ) ) + if( !segmentIntersectsArc( pc.a1, pc.a2, beArcPos, beArcRadius, + beArcStartAngle.AsDegrees(), beArcEndAngle.AsDegrees() ) ) + result.push_back( pc ); + } + + return result; +} + + +std::vector<path_connection> CU_SHAPE_CIRCLE::Paths( const BE_SHAPE_POINT& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const +{ + std::vector<path_connection> result; + + double R = this->GetRadius(); + VECTOR2I center = this->GetPos(); + VECTOR2I point = aS2.GetPos(); + double weight = ( center - point ).EuclideanNorm() - R; + + if( weight > aMaxWeight ) + return result; + + path_connection pc; + pc.weight = weight; + pc.a2 = point; + pc.a1 = center + ( point - center ).Resize( R ); + + result.push_back( pc ); + return result; +} + + +std::vector<path_connection> CU_SHAPE_CIRCLE::Paths( const CU_SHAPE_CIRCLE& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const +{ + std::vector<path_connection> result; + + double R1 = this->GetRadius(); + double R2 = aS2.GetRadius(); + VECTOR2I C1 = this->GetPos(); + VECTOR2I C2 = aS2.GetPos(); + + if( ( C1 - C2 ).SquaredEuclideanNorm() < ( R1 - R2 ) * ( R1 - R2 ) ) + { + // One of the circles is inside the other + return result; + } + + double weight = ( C1 - C2 ).EuclideanNorm() - R1 - R2; + + if( weight > aMaxWeight || weight < 0 ) + return result; + + path_connection pc; + pc.weight = weight; + pc.a1 = ( C2 - C1 ).Resize( R1 ) + C1; + pc.a2 = ( C1 - C2 ).Resize( R2 ) + C2; + result.push_back( pc ); + return result; +} + + +std::vector<path_connection> CU_SHAPE_SEGMENT::Paths( const CU_SHAPE_CIRCLE& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const +{ + std::vector<path_connection> result; + + VECTOR2I s_start = this->GetStart(); + VECTOR2I s_end = this->GetEnd(); + double halfWidth = this->GetWidth() / 2; + + EDA_ANGLE trackAngle( s_end - s_start ); + VECTOR2I pointPos = aS2.GetPos(); + + double length = ( s_start - s_end ).EuclideanNorm(); + double projectedPos = cos( trackAngle.AsRadians() ) * ( pointPos.x - s_start.x ) + + sin( trackAngle.AsRadians() ) * ( pointPos.y - s_start.y ); + + if( ( projectedPos <= 0 ) || ( s_start == s_end ) ) + { + CU_SHAPE_CIRCLE csc( s_start, halfWidth ); + return csc.Paths( aS2, aMaxWeight, aMaxSquaredWeight ); + } + if( projectedPos >= length ) + { + CU_SHAPE_CIRCLE csc( s_end, halfWidth ); + return csc.Paths( aS2, aMaxWeight, aMaxSquaredWeight ); + } + + double radius = aS2.GetRadius(); + double trackSide = ( s_end - s_start ).Cross( pointPos - s_start ) > 0 ? 1 : -1; + + path_connection pc; + pc.a1 = s_start + ( s_end - s_start ).Resize( projectedPos ) + + ( s_end - s_start ).Perpendicular().Resize( halfWidth ) * trackSide; + pc.a2 = ( pc.a1 - pointPos ).Resize( radius ) + pointPos; + pc.weight = ( pc.a2 - pc.a1 ).SquaredEuclideanNorm(); + + if( pc.weight <= aMaxSquaredWeight ) + { + pc.weight = sqrt( pc.weight ); + result.push_back( pc ); + } + return result; +} + + +std::vector<path_connection> CU_SHAPE_CIRCLE::Paths( const CU_SHAPE_ARC& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const +{ + std::vector<path_connection> result; + + VECTOR2I circlePos = this->GetPos(); + VECTOR2I arcPos = aS2.GetPos(); + + double circleRadius = this->GetRadius(); + double arcRadius = aS2.GetRadius(); + + VECTOR2I startPoint = aS2.GetStartPoint(); + VECTOR2I endPoint = aS2.GetEndPoint(); + + CU_SHAPE_CIRCLE csc( arcPos, arcRadius + aS2.GetWidth() / 2 ); + + if( ( circlePos - arcPos ).EuclideanNorm() > arcRadius + circleRadius ) + { + std::vector<path_connection> pcs = this->Paths( csc, aMaxWeight, aMaxSquaredWeight ); + + if( pcs.size() == 1 ) + { + EDA_ANGLE testAngle = aS2.AngleBetweenStartAndEnd( pcs[0].a2 ); + + if( testAngle < aS2.GetEndAngle() ) + { + result.push_back( pcs[0] ); + return result; + } + } + } + + CU_SHAPE_CIRCLE csc1( startPoint, aS2.GetWidth() / 2 ); + CU_SHAPE_CIRCLE csc2( endPoint, aS2.GetWidth() / 2 ); + + path_connection* bestPath = nullptr; + + + std::vector<path_connection> pcs1 = this->Paths( csc1, aMaxWeight, aMaxSquaredWeight ); + std::vector<path_connection> pcs2 = this->Paths( csc2, aMaxWeight, aMaxSquaredWeight ); + + for( path_connection& pc : pcs1 ) + { + if( !bestPath || ( ( bestPath->weight > pc.weight ) && ( pc.weight > 0 ) ) ) + bestPath = &pc; + } + + for( path_connection& pc : pcs2 ) + { + if( !bestPath || ( ( bestPath->weight > pc.weight ) && ( pc.weight > 0 ) ) ) + bestPath = &pc; + } + + // If the circle center is insde the arc ring + + path_connection pc3; + + if( ( circlePos - arcPos ).SquaredEuclideanNorm() < arcRadius * arcRadius ) + { + if( circlePos != arcPos ) // The best path is already found otherwise + { + EDA_ANGLE testAngle = aS2.AngleBetweenStartAndEnd( circlePos ); + + if( testAngle < aS2.GetEndAngle() ) + { + pc3.weight = arcRadius - ( circlePos - arcPos ).EuclideanNorm() - circleRadius; + pc3.a1 = circlePos + ( circlePos - arcPos ).Resize( circleRadius ); + pc3.a2 = arcPos + ( circlePos - arcPos ).Resize( arcRadius - aS2.GetWidth() / 2 ); + + if( !bestPath || ( ( bestPath->weight > pc3.weight ) && ( pc3.weight > 0 ) ) ) + bestPath = &pc3; + } + } + } + + if( bestPath && bestPath->weight > 0 ) + { + result.push_back( *bestPath ); + } + + return result; +} + + +std::vector<path_connection> CU_SHAPE_SEGMENT::Paths( const CU_SHAPE_ARC& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const +{ + std::vector<path_connection> result; + + VECTOR2I s_start = this->GetStart(); + VECTOR2I s_end = this->GetEnd(); + double halfWidth1 = this->GetWidth() / 2; + + VECTOR2I arcPos = aS2.GetPos(); + double arcRadius = aS2.GetRadius(); + double halfWidth2 = aS2.GetWidth() / 2; + + + CU_SHAPE_CIRCLE csc( arcPos, arcRadius + halfWidth2 ); + + std::vector<path_connection> pcs; + pcs = this->Paths( csc, aMaxWeight, aMaxSquaredWeight ); + + if ( pcs.size() < 1 ) + return result; + + VECTOR2I circlePoint; + EDA_ANGLE testAngle; + + if( pcs.size() > 0 ) + { + circlePoint = pcs[0].a1; + testAngle = ( aS2.AngleBetweenStartAndEnd( pcs[0].a1 ) ); + } + if( testAngle < aS2.GetEndAngle() && pcs.size() > 0 ) + { + result.push_back( pcs[0] ); + return result; + } + + CU_SHAPE_CIRCLE csc1( aS2.GetStartPoint(), halfWidth2 ); + CU_SHAPE_CIRCLE csc2( aS2.GetEndPoint(), halfWidth2 ); + path_connection* bestPath = nullptr; + + + std::vector<path_connection> pcs1 = this->Paths( csc1, aMaxWeight, aMaxSquaredWeight ); + + for( path_connection& pc : pcs1 ) + { + if( !bestPath || ( bestPath->weight > pc.weight ) ) + { + bestPath = &pc; + } + } + + std::vector<path_connection> pcs2 = this->Paths( csc2, aMaxWeight, aMaxSquaredWeight ); + + for( path_connection& pc : pcs2 ) + { + if( !bestPath || ( bestPath->weight > pc.weight ) ) + { + bestPath = &pc; + } + } + + CU_SHAPE_CIRCLE csc3( s_start, halfWidth1 ); + CU_SHAPE_CIRCLE csc4( s_end, halfWidth1 ); + + std::vector<path_connection> pcs3 = csc3.Paths( aS2, aMaxWeight, aMaxSquaredWeight ); + + for( path_connection& pc : pcs3 ) + { + if( !bestPath || ( bestPath->weight > pc.weight ) ) + { + bestPath = &pc; + } + } + + + std::vector<path_connection> pcs4 = csc4.Paths( aS2, aMaxWeight, aMaxSquaredWeight ); + + for( path_connection& pc : pcs4 ) + { + if( !bestPath || ( bestPath->weight > pc.weight ) ) + { + bestPath = &pc; + } + } + + if( bestPath ) + { + result.push_back( *bestPath ); + } + + return result; +} + +// Function to compute the projection of point P onto the line segment AB +VECTOR2I closestPointOnSegment( const VECTOR2I& A, const VECTOR2I& B, const VECTOR2I& P ) +{ + if( A == B ) + return A; + if( A == P ) + return A; + + VECTOR2I AB = B - A; + VECTOR2I AP = P - A; + + double t = float( AB.Dot( AP ) ) / float( AB.SquaredEuclideanNorm() ); + + // Clamp t to the range [0, 1] to restrict the projection to the segment + t = std::max( 0.0, std::min( 1.0, t ) ); + + return A + ( AB * t ); +} + + +std::vector<path_connection> CU_SHAPE_SEGMENT::Paths( const CU_SHAPE_SEGMENT& aS2, + double aMaxWeight, + double aMaxSquaredWeight ) const +{ + std::vector<path_connection> result; + + VECTOR2I A( this->GetStart() ); + VECTOR2I B( this->GetEnd() ); + double halfWidth1 = this->GetWidth() / 2; + + + VECTOR2I C( aS2.GetStart() ); + VECTOR2I D( aS2.GetEnd() ); + double halfWidth2 = aS2.GetWidth() / 2; + + VECTOR2I P1 = closestPointOnSegment( A, B, C ); + VECTOR2I P2 = closestPointOnSegment( A, B, D ); + VECTOR2I P3 = closestPointOnSegment( C, D, A ); + VECTOR2I P4 = closestPointOnSegment( C, D, B ); + + // Calculate all possible squared distances between the segments + double dist1 = ( P1 - C ).SquaredEuclideanNorm(); + double dist2 = ( P2 - D ).SquaredEuclideanNorm(); + double dist3 = ( P3 - A ).SquaredEuclideanNorm(); + double dist4 = ( P4 - B ).SquaredEuclideanNorm(); + + // Find the minimum squared distance and update closest points + double min_dist = dist1; + VECTOR2I closest1 = P1; + VECTOR2I closest2 = C; + + if( dist2 < min_dist ) + { + min_dist = dist2; + closest1 = P2; + closest2 = D; + } + + if( dist3 < min_dist ) + { + min_dist = dist3; + closest1 = A; + closest2 = P3; + } + + if( dist4 < min_dist ) + { + min_dist = dist4; + closest1 = B; + closest2 = P4; + } + + + path_connection pc; + pc.a1 = closest1 + ( closest2 - closest1 ).Resize( halfWidth1 ); + pc.a2 = closest2 + ( closest1 - closest2 ).Resize( halfWidth2 ); + pc.weight = sqrt( min_dist ) - halfWidth1 - halfWidth2; + + if( pc.weight <= aMaxWeight ) + { + result.push_back( pc ); + } + return result; +} + + +std::vector<path_connection> CU_SHAPE_CIRCLE::Paths( const BE_SHAPE_CIRCLE& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const +{ + std::vector<path_connection> result; + + double R1 = this->GetRadius(); + double R2 = aS2.GetRadius(); + VECTOR2I center1 = this->GetPos(); + VECTOR2I center2 = aS2.GetPos(); + double dist = ( center1 - center2 ).EuclideanNorm(); + + if( dist > aMaxWeight || dist == 0 ) + { + return result; + } + + double weight = sqrt ( dist*dist - R2 * R2 ) - R1; + double theta = asin( R2 / dist ); + double psi = acos( R2 / dist ); + + if( weight > aMaxWeight ) + { + return result; + } + + path_connection pc; + pc.weight = weight; + + double circleAngle = EDA_ANGLE( center2 - center1 ).AsRadians(); + + VECTOR2I pStart; + VECTOR2I pEnd; + + pStart = VECTOR2I( R1 * cos( theta + circleAngle ), R1 * sin( theta + circleAngle ) ); + pStart += center1; + pEnd = VECTOR2I( -R2 * cos( psi - circleAngle ), R2 * sin( psi - circleAngle ) ); + pEnd += center2; + + + pc.a1 = pStart; + pc.a2 = pEnd; + result.push_back( pc ); + + pStart = VECTOR2I( R1 * cos( -theta + circleAngle ), R1 * sin( -theta + circleAngle ) ); + pStart += center1; + pEnd = VECTOR2I( -R2 * cos( -psi - circleAngle ), R2 * sin( -psi - circleAngle ) ); + pEnd += center2; + + pc.a1 = pStart; + pc.a2 = pEnd; + + result.push_back( pc ); + return result; +} + + +std::vector<path_connection> CU_SHAPE_ARC::Paths( const BE_SHAPE_POINT& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const +{ + std::vector<path_connection> result; + VECTOR2I point = aS2.GetPos(); + VECTOR2I arcCenter = this->GetPos(); + + double radius = this->GetRadius(); + double width = this->GetWidth(); + + EDA_ANGLE angle( point - arcCenter ); + + while( angle < this->GetStartAngle() ) + angle += ANGLE_360; + while( angle > this->GetEndAngle() + ANGLE_360 ) + angle -= ANGLE_360; + + if( angle < this->GetEndAngle() ) + { + if( ( point - arcCenter ).SquaredEuclideanNorm() > radius * radius ) + { + CU_SHAPE_CIRCLE circle( arcCenter, radius + width / 2 ); + return circle.Paths( aS2, aMaxWeight, aMaxSquaredWeight ); + } + else + { + path_connection pc; + pc.weight = ( radius - width / 2 ) - ( point - arcCenter ).EuclideanNorm(); + pc.a1 = ( point - arcCenter ).Resize( radius - width / 2 ) + arcCenter; + pc.a2 = point; + + if( pc.weight > 0 && pc.weight < aMaxWeight ) + result.push_back( pc ); + + return result; + } + } + else + { + VECTOR2I nearestPoint; + + if( ( point - this->GetStartPoint() ).SquaredEuclideanNorm() + > ( point - this->GetEndPoint() ).SquaredEuclideanNorm() ) + nearestPoint = this->GetEndPoint(); + else + nearestPoint = this->GetStartPoint(); + + CU_SHAPE_CIRCLE circle( nearestPoint, width / 2 ); + return circle.Paths( aS2, aMaxWeight, aMaxSquaredWeight ); + } + return result; +} + + +std::vector<path_connection> CU_SHAPE_ARC::Paths( const CU_SHAPE_ARC& aS2, double aMaxWeight, + double aMaxSquaredWeight ) const +{ + std::vector<path_connection> result; + + double R1 = this->GetRadius(); + double R2 = aS2.GetRadius(); + + VECTOR2I C1 = this->GetPos(); + VECTOR2I C2 = aS2.GetPos(); + + path_connection bestPath; + bestPath.weight = std::numeric_limits<double>::infinity(); + CU_SHAPE_CIRCLE csc1( C1, R1 + this->GetWidth() / 2 ); + CU_SHAPE_CIRCLE csc2( C2, R2 + aS2.GetWidth() / 2 ); + + CU_SHAPE_CIRCLE csc3( this->GetStartPoint(), this->GetWidth() / 2 ); + CU_SHAPE_CIRCLE csc4( this->GetEndPoint(), this->GetWidth() / 2 ); + CU_SHAPE_CIRCLE csc5( aS2.GetStartPoint(), aS2.GetWidth() / 2 ); + CU_SHAPE_CIRCLE csc6( aS2.GetEndPoint(), aS2.GetWidth() / 2 ); + + std::vector<path_connection> pcs0 = csc1.Paths( csc2, aMaxWeight, aMaxSquaredWeight ); + + std::vector<path_connection> pcs1 = this->Paths( csc2, aMaxWeight, aMaxSquaredWeight ); + std::vector<path_connection> pcs2 = csc1.Paths( aS2, aMaxWeight, aMaxSquaredWeight ); + + std::vector<path_connection> pcs3 = this->Paths( csc5, aMaxWeight, aMaxSquaredWeight ); + std::vector<path_connection> pcs4 = this->Paths( csc6, aMaxWeight, aMaxSquaredWeight ); + + std::vector<path_connection> pcs5 = csc3.Paths( aS2, aMaxWeight, aMaxSquaredWeight ); + std::vector<path_connection> pcs6 = csc4.Paths( aS2, aMaxWeight, aMaxSquaredWeight ); + + for( std::vector<path_connection> pcs : { pcs0, pcs1, pcs2 } ) + { + for( path_connection& pc : pcs ) + { + EDA_ANGLE testAngle1 = this->AngleBetweenStartAndEnd( pc.a1 ); + EDA_ANGLE testAngle2 = aS2.AngleBetweenStartAndEnd( pc.a2 ); + + if( ( testAngle1 < this->GetEndAngle() ) && ( testAngle2 < aS2.GetEndAngle() ) + && ( bestPath.weight > pc.weight ) ) + { + bestPath = pc; + } + } + } + + for( std::vector<path_connection> pcs : { pcs3, pcs4, pcs5, pcs6 } ) + { + for( path_connection& pc : pcs ) + { + if( bestPath.weight > pc.weight ) + { + bestPath = pc; + } + } + } + + if( bestPath.weight != std::numeric_limits<double>::infinity() ) + { + result.push_back( bestPath ); + } + + return result; +} + + +bool segmentIntersectsCircle( VECTOR2I p1, VECTOR2I p2, VECTOR2I center, double radius ) +{ + // Vector from p1 to p2 + VECTOR2I D = p2 - p1; + // Vector from p1 to center + VECTOR2I F = p1 - center; + + VECTOR2D d( D.x, D.y ); + VECTOR2D f( F.x, F.y ); + + double a = d.x * d.x + d.y * d.y; + double b = 2 * ( f.x * d.x + f.y * d.y ); + double c = ( f.x * f.x + f.y * f.y ) - radius * radius; + + double discriminant = b * b - 4 * a * c; + + if( discriminant < 0 ) + { + return false; // No intersection + } + else + { + discriminant = sqrt( discriminant ); + double t1 = ( -b - discriminant ) / ( 2 * a ); + double t2 = ( -b + discriminant ) / ( 2 * a ); + + if( ( t1 >= 0 && t1 <= 1 ) || ( t2 >= 0 && t2 <= 1 ) ) + { + return true; // Intersection within the segment + } + + return false; + } +} + +bool SegmentIntersectsBoard( VECTOR2I aP1, VECTOR2I aP2, std::vector<BOARD_ITEM*> aBe, + std::vector<const BOARD_ITEM*> aDontTestAgainst ) +{ + for( BOARD_ITEM* be : aBe ) + { + if( count( aDontTestAgainst.begin(), aDontTestAgainst.end(), be ) > 0 ) + continue; + + PCB_SHAPE* d = static_cast<PCB_SHAPE*>( be ); + if( !d ) + continue; + + switch( d->GetShape() ) + { + case SHAPE_T::SEGMENT: + if( segments_intersect( aP1, aP2, d->GetStart(), d->GetEnd() ) ) + return false; + break; + + case SHAPE_T::RECTANGLE: + { + VECTOR2I c1 = d->GetStart(); + VECTOR2I c2( d->GetStart().x, d->GetEnd().y ); + VECTOR2I c3 = d->GetEnd(); + VECTOR2I c4( d->GetEnd().x, d->GetStart().y ); + + if( segments_intersect( aP1, aP2, c1, c2 ) || segments_intersect( aP1, aP2, c2, c3 ) + || segments_intersect( aP1, aP2, c3, c4 ) + || segments_intersect( aP1, aP2, c4, c1 ) ) + { + return false; + } + break; + } + case SHAPE_T::POLY: + { + std::vector<VECTOR2I> points; + d->DupPolyPointsList( points ); + + if ( points.size() < 2 ) + break; + VECTOR2I prevPoint = points.back(); + + for( auto p : points ) + { + if( segments_intersect( aP1, aP2, prevPoint, p ) ) + return false; + prevPoint = p; + } + break; + } + case SHAPE_T::CIRCLE: + { + VECTOR2I center = d->GetCenter(); + double radius = d->GetRadius(); + + if( segmentIntersectsCircle( aP1, aP2, center, radius ) ) + return false; + + break; + } + + + case SHAPE_T::ARC: + { + VECTOR2I center = d->GetCenter(); + double radius = d->GetRadius(); + + EDA_ANGLE A, B; + d->CalcArcAngles( A, B ); + + if( segmentIntersectsArc( aP1, aP2, center, radius, A.AsDegrees(), B.AsDegrees() ) ) + return false; + + break; + } + + + default: break; + } + } + return true; +} + +bool CheckPathValidity( VECTOR2I aP1, VECTOR2I aP2, std::vector<BOARD_ITEM*> aBe, + std::vector<const BOARD_ITEM*> aDontTestAgainst ) +{ + return false; +} + +std::vector<path_connection> GetPaths( CREEP_SHAPE* aS1, CREEP_SHAPE* aS2, double aMaxWeight ) +{ + double maxWeight = aMaxWeight; + double maxWeightSquared = maxWeight * maxWeight; + std::vector<path_connection> result; + + CU_SHAPE_SEGMENT* cusegment1 = dynamic_cast<CU_SHAPE_SEGMENT*>( aS1 ); + CU_SHAPE_SEGMENT* cusegment2 = dynamic_cast<CU_SHAPE_SEGMENT*>( aS2 ); + CU_SHAPE_CIRCLE* cucircle1 = dynamic_cast<CU_SHAPE_CIRCLE*>( aS1 ); + CU_SHAPE_CIRCLE* cucircle2 = dynamic_cast<CU_SHAPE_CIRCLE*>( aS2 ); + CU_SHAPE_ARC* cuarc1 = dynamic_cast<CU_SHAPE_ARC*>( aS1 ); + CU_SHAPE_ARC* cuarc2 = dynamic_cast<CU_SHAPE_ARC*>( aS2 ); + + + BE_SHAPE_POINT* bepoint1 = dynamic_cast<BE_SHAPE_POINT*>( aS1 ); + BE_SHAPE_POINT* bepoint2 = dynamic_cast<BE_SHAPE_POINT*>( aS2 ); + BE_SHAPE_CIRCLE* becircle1 = dynamic_cast<BE_SHAPE_CIRCLE*>( aS1 ); + BE_SHAPE_CIRCLE* becircle2 = dynamic_cast<BE_SHAPE_CIRCLE*>( aS2 ); + BE_SHAPE_ARC* bearc1 = dynamic_cast<BE_SHAPE_ARC*>( aS1 ); + BE_SHAPE_ARC* bearc2 = dynamic_cast<BE_SHAPE_ARC*>( aS2 ); + + // Cu to Cu + + if( cuarc1 && cuarc2 ) + return cuarc1->Paths( *cuarc2, maxWeight, maxWeightSquared ); + if( cuarc1 && cucircle2 ) + return cuarc1->Paths( *cucircle2, maxWeight, maxWeightSquared ); + if( cuarc1 && cusegment2 ) + return cuarc1->Paths( *cusegment2, maxWeight, maxWeightSquared ); + if( cucircle1 && cuarc2 ) + return cucircle1->Paths( *cuarc2, maxWeight, maxWeightSquared ); + if( cucircle1 && cucircle2 ) + return cucircle1->Paths( *cucircle2, maxWeight, maxWeightSquared ); + if( cucircle1 && cusegment2 ) + return cucircle1->Paths( *cusegment2, maxWeight, maxWeightSquared ); + if( cusegment1 && cuarc2 ) + return cusegment1->Paths( *cuarc2, maxWeight, maxWeightSquared ); + if( cusegment1 && cucircle2 ) + return cusegment1->Paths( *cucircle2, maxWeight, maxWeightSquared ); + if( cusegment1 && cusegment2 ) + return cusegment1->Paths( *cusegment2, maxWeight, maxWeightSquared ); + + //Reversed + + + if( cuarc2 && cuarc1 ) + return cuarc1->Paths( *cuarc2, maxWeight, maxWeightSquared ); + if( cuarc2 && cucircle1 ) + return cucircle1->Paths( *cuarc2, maxWeight, maxWeightSquared ); + if( cuarc2 && cusegment1 ) + return cusegment1->Paths( *cuarc2, maxWeight, maxWeightSquared ); + if( cucircle2 && cuarc1 ) + return cuarc1->Paths( *cucircle2, maxWeight, maxWeightSquared ); + if( cucircle2 && cucircle1 ) + return cucircle1->Paths( *cucircle2, maxWeight, maxWeightSquared ); + if( cucircle2 && cusegment1 ) + return cusegment1->Paths( *cucircle2, maxWeight, maxWeightSquared ); + if( cusegment2 && cuarc1 ) + return cuarc1->Paths( *cusegment2, maxWeight, maxWeightSquared ); + if( cusegment2 && cucircle1 ) + return cucircle1->Paths( *cusegment2, maxWeight, maxWeightSquared ); + if( cusegment2 && cusegment1 ) + return cusegment1->Paths( *cusegment2, maxWeight, maxWeightSquared ); + + + // Cu to Be + + if( cuarc1 && bearc2 ) + return cuarc1->Paths( *bearc2, maxWeight, maxWeightSquared ); + if( cuarc1 && becircle2 ) + return cuarc1->Paths( *becircle2, maxWeight, maxWeightSquared ); + if( cuarc1 && bepoint2 ) + return cuarc1->Paths( *bepoint2, maxWeight, maxWeightSquared ); + if( cucircle1 && bearc2 ) + return cucircle1->Paths( *bearc2, maxWeight, maxWeightSquared ); + if( cucircle1 && becircle2 ) + return cucircle1->Paths( *becircle2, maxWeight, maxWeightSquared ); + if( cucircle1 && bepoint2 ) + return cucircle1->Paths( *bepoint2, maxWeight, maxWeightSquared ); + if( cusegment1 && bearc2 ) + return cusegment1->Paths( *bearc2, maxWeight, maxWeightSquared ); + if( cusegment1 && becircle2 ) + return cusegment1->Paths( *becircle2, maxWeight, maxWeightSquared ); + if( cusegment1 && bepoint2 ) + return cusegment1->Paths( *bepoint2, maxWeight, maxWeightSquared ); + + // Reversed + + + if( bearc2 && bearc1 ) + return bearc1->Paths( *bearc2, maxWeight, maxWeightSquared ); + if( bearc2 && becircle1 ) + return becircle1->Paths( *bearc2, maxWeight, maxWeightSquared ); + if( bearc2 && bepoint1 ) + return bepoint1->Paths( *bearc2, maxWeight, maxWeightSquared ); + if( cucircle2 && bearc1 ) + return bearc1->Paths( *cucircle2, maxWeight, maxWeightSquared ); + if( cucircle2 && becircle1 ) + return becircle1->Paths( *cucircle2, maxWeight, maxWeightSquared ); + if( cucircle2 && bepoint1 ) + return bepoint1->Paths( *cucircle2, maxWeight, maxWeightSquared ); + if( cusegment2 && bearc1 ) + return bearc1->Paths( *cusegment2, maxWeight, maxWeightSquared ); + if( cusegment2 && becircle1 ) + return becircle1->Paths( *cusegment2, maxWeight, maxWeightSquared ); + if( cusegment2 && bepoint1 ) + return bepoint1->Paths( *cusegment2, maxWeight, maxWeightSquared ); + + + // Be to Be + + if( bearc1 && bearc2 ) + return bearc1->Paths( *bearc2, maxWeight, maxWeightSquared ); + if( bearc1 && becircle2 ) + return bearc1->Paths( *becircle2, maxWeight, maxWeightSquared ); + if( bearc1 && bepoint2 ) + return bearc1->Paths( *bepoint2, maxWeight, maxWeightSquared ); + if( becircle1 && bearc2 ) + return becircle1->Paths( *bearc2, maxWeight, maxWeightSquared ); + if( becircle1 && becircle2 ) + return becircle1->Paths( *becircle2, maxWeight, maxWeightSquared ); + if( becircle1 && bepoint2 ) + return becircle1->Paths( *bepoint2, maxWeight, maxWeightSquared ); + if( bepoint1 && bearc2 ) + return bepoint1->Paths( *bearc2, maxWeight, maxWeightSquared ); + if( bepoint1 && becircle2 ) + return bepoint1->Paths( *becircle2, maxWeight, maxWeightSquared ); + if( bepoint1 && bepoint2 ) + return bepoint1->Paths( *bepoint2, maxWeight, maxWeightSquared ); + + // Reversed + + + if( bearc2 && bearc1 ) + return bearc1->Paths( *bearc2, maxWeight, maxWeightSquared ); + if( bearc2 && becircle1 ) + return becircle1->Paths( *bearc2, maxWeight, maxWeightSquared ); + if( bearc2 && bepoint1 ) + return bepoint1->Paths( *bearc2, maxWeight, maxWeightSquared ); + if( becircle2 && bearc1 ) + return bearc1->Paths( *becircle2, maxWeight, maxWeightSquared ); + if( becircle2 && becircle1 ) + return becircle1->Paths( *becircle2, maxWeight, maxWeightSquared ); + if( becircle2 && bepoint1 ) + return bepoint1->Paths( *becircle2, maxWeight, maxWeightSquared ); + if( bepoint2 && bearc1 ) + return bearc1->Paths( *bepoint2, maxWeight, maxWeightSquared ); + if( bepoint2 && becircle1 ) + return becircle1->Paths( *bepoint2, maxWeight, maxWeightSquared ); + if( bepoint2 && bepoint1 ) + return bepoint1->Paths( *bepoint2, maxWeight, maxWeightSquared ); + + return result; +} + +double +CreepageGraph::Solve( GraphNode* aFrom, GraphNode* aTo, + std::vector<GraphConnection*>& aResult ) // Change to vector of pointers +{ + if( !aFrom || !aTo ) + return 0; + + if( aFrom == aTo ) + return 0; + + // Dijkstra's algorithm for shortest path + std::unordered_map<GraphNode*, double> distances; + std::unordered_map<GraphNode*, GraphNode*> previous; + + auto cmp = [&distances]( GraphNode* left, GraphNode* right ) + { + return distances[left] > distances[right]; + }; + std::priority_queue<GraphNode*, std::vector<GraphNode*>, decltype( cmp )> pq( cmp ); + + // Initialize distances to infinity for all nodes except the starting node + for( GraphNode* node : m_nodes ) + { + distances[node] = std::numeric_limits<double>::infinity(); + } + distances[aFrom] = 0.0; + pq.push( aFrom ); + + // Dijkstra's main loop + while( !pq.empty() ) + { + GraphNode* current = pq.top(); + pq.pop(); + + if( current == aTo ) + { + break; // Shortest path found + } + + // Traverse neighbors + for( GraphConnection* connection : current->m_connections ) + { + GraphNode* neighbor = connection->n1 == current ? connection->n2 : connection->n1; + double alt = distances[current] + + connection->m_path.weight; // Calculate alternative path cost + if( alt < distances[neighbor] ) + { + distances[neighbor] = alt; + previous[neighbor] = current; + pq.push( neighbor ); + } + } + } + + double pathWeight = distances[aTo]; + + // If aTo is unreachable, return infinity + if( pathWeight == std::numeric_limits<double>::infinity() ) + { + return std::numeric_limits<double>::infinity(); + } + + // Trace back the path from aTo to aFrom + GraphNode* step = aTo; + + while( step != aFrom ) + { + GraphNode* prevNode = previous[step]; + for( GraphConnection* connection : step->m_connections ) + { + if( ( connection->n1 == prevNode && connection->n2 == step ) + || ( connection->n1 == step && connection->n2 == prevNode ) ) + { + aResult.push_back( connection ); + break; + } + } + step = prevNode; + } + + return pathWeight; +} + +void CreepageGraph::Addshape( const SHAPE& aShape, GraphNode* aConnectTo, BOARD_ITEM* aParent ) +{ + CREEP_SHAPE* newshape = nullptr; + + if( !aConnectTo ) + return; + + switch( aShape.Type() ) + { + case SH_SEGMENT: + { + const SHAPE_SEGMENT& segment = dynamic_cast<const SHAPE_SEGMENT&>( aShape ); + CU_SHAPE_SEGMENT* cuseg = + new CU_SHAPE_SEGMENT( segment.GetSeg().A, segment.GetSeg().B, segment.GetWidth() ); + newshape = dynamic_cast<CREEP_SHAPE*>( cuseg ); + break; + } + case SH_CIRCLE: + { + const SHAPE_CIRCLE& circle = dynamic_cast<const SHAPE_CIRCLE&>( aShape ); + CU_SHAPE_CIRCLE* cucircle = new CU_SHAPE_CIRCLE( circle.GetCenter(), circle.GetRadius() ); + newshape = dynamic_cast<CREEP_SHAPE*>( cucircle ); + break; + } + case SH_ARC: + { + const SHAPE_ARC& arc = dynamic_cast<const SHAPE_ARC&>( aShape ); + EDA_ANGLE alpha, beta; + VECTOR2I start, end; + + EDA_SHAPE edaArc( SHAPE_T::ARC, 0, FILL_T::NO_FILL ); + + if( arc.IsClockwise() ) + { + edaArc.SetArcGeometry( arc.GetP0(), arc.GetArcMid(), arc.GetP1() ); + start = arc.GetP0(); + end = arc.GetP1(); + } + else + { + edaArc.SetArcGeometry( arc.GetP1(), arc.GetArcMid(), arc.GetP0() ); + start = arc.GetP1(); + end = arc.GetP0(); + } + + edaArc.CalcArcAngles( alpha, beta ); + + CU_SHAPE_ARC* cuarc = new CU_SHAPE_ARC( edaArc.getCenter(), edaArc.GetRadius(), alpha, beta, + arc.GetP0(), arc.GetP1() ); + cuarc->SetWidth( arc.GetWidth() ); + newshape = dynamic_cast<CREEP_SHAPE*>( cuarc ); + break; + } + case SH_COMPOUND: + { + int nbShapes = static_cast<const SHAPE_COMPOUND*>( &aShape )->Shapes().size(); + for( const SHAPE* subshape : ( static_cast<const SHAPE_COMPOUND*>( &aShape )->Shapes() ) ) + { + if( subshape ) + { + // We don't want to add shape for the inner rectangle of rounded rectangles + if ( !( ( subshape->Type() == SH_RECT ) && ( nbShapes == 5 ) )) + Addshape( *subshape, aConnectTo, aParent ); + } + } + break; + } + case SH_POLY_SET: + { + const SHAPE_POLY_SET& polySet = dynamic_cast<const SHAPE_POLY_SET&>( aShape ); + + for( auto it = polySet.CIterateSegmentsWithHoles(); it; it++ ) + { + const SEG object = *it; + SHAPE_SEGMENT segment( object.A, object.B ); + Addshape( segment, aConnectTo, aParent ); + } + break; + } + case SH_LINE_CHAIN: + { + const SHAPE_LINE_CHAIN& lineChain = dynamic_cast<const SHAPE_LINE_CHAIN&>( aShape ); + + VECTOR2I prevPoint = lineChain.CLastPoint(); + + for( auto point : lineChain.CPoints() ) + { + SHAPE_SEGMENT segment( point, prevPoint ); + prevPoint = point; + Addshape( segment, aConnectTo, aParent ); + } + break; + } + case SH_RECT: + { + const SHAPE_RECT& rect = dynamic_cast<const SHAPE_RECT&>( aShape ); + + VECTOR2I point0 = rect.GetPosition(); + VECTOR2I point1 = rect.GetPosition() + VECTOR2I( rect.GetSize().x, 0 ); + VECTOR2I point2 = rect.GetPosition() + rect.GetSize(); + VECTOR2I point3 = rect.GetPosition() + VECTOR2I( 0, rect.GetSize().y ); + + Addshape( SHAPE_SEGMENT( point0, point1 ), aConnectTo, aParent ); + Addshape( SHAPE_SEGMENT( point1, point2 ), aConnectTo, aParent ); + Addshape( SHAPE_SEGMENT( point2, point3 ), aConnectTo, aParent ); + Addshape( SHAPE_SEGMENT( point3, point0 ), aConnectTo, aParent ); + break; + } + default: break; + } + + if( !newshape ) + return; + + GraphNode* gnShape; + + newshape->SetParent( aParent ); + + switch( aShape.Type() ) + { + case SH_SEGMENT: gnShape = AddNode( GraphNode::SEGMENT, newshape, newshape->GetPos() ); break; + case SH_CIRCLE: gnShape = AddNode( GraphNode::CIRCLE, newshape, newshape->GetPos() ); break; + case SH_ARC: gnShape = AddNode( GraphNode::ARC, newshape, newshape->GetPos() ); break; + default: break; + } + + if( gnShape ) + { + m_shapeCollection.push_back( newshape ); + gnShape->m_net = aConnectTo->m_net; + GraphConnection* gc = AddConnection( gnShape, aConnectTo ); + + if( gc ) + gc->m_path.m_show = false; + } + else + { + delete newshape; + newshape = nullptr; + } +} + +void CreepageGraph::GeneratePaths( double aMaxWeight, PCB_LAYER_ID aLayer, bool aGenerateBoardEdges ) +{ + std::vector<GraphNode*> nodes1 = m_nodes; + std::vector<GraphNode*> nodes2 = m_nodes; + + + for( GraphNode* gn1 : nodes1 ) + { + nodes2.erase( nodes2.begin() ); + + if( !gn1 ) + continue; + + if( !gn1->m_parent ) + continue; + + if( !gn1->m_connectDirectly ) + continue; + + if( gn1->m_type == GraphNode::TYPE::VIRTUAL ) + continue; + + + for( GraphNode* gn2 : nodes2 ) + { + if( !gn2 ) + continue; + + if( !gn2->m_parent ) + continue; + + if( gn1->m_parent == gn2->m_parent ) + continue; + + if( !gn2->m_connectDirectly ) + continue; + + if( gn2->m_type == GraphNode::TYPE::VIRTUAL ) + continue; + + if( !aGenerateBoardEdges && !gn1->m_parent->IsConductive() + && !gn2->m_parent->IsConductive() ) + continue; + + if( ( gn1->m_net == gn2->m_net ) && ( gn1->m_parent->IsConductive() ) + && ( gn2->m_parent->IsConductive() ) ) + continue; + + for( path_connection pc : GetPaths( gn1->m_parent, gn2->m_parent, aMaxWeight ) ) + { + std::vector<const BOARD_ITEM*> IgnoreForTest; + IgnoreForTest.push_back( gn1->m_parent->GetParent() ); + IgnoreForTest.push_back( gn2->m_parent->GetParent() ); + + if( !pc.isValid( m_board, aLayer, m_boardEdge, IgnoreForTest, m_boardOutline, + { false, true } ) ) + continue; + + GraphNode* connect1; + GraphNode* connect2; + + if( gn1->m_parent->GetType() == CREEP_SHAPE::TYPE::POINT ) + { + connect1 = gn1; + } + else + { + GraphNode* gnt = AddNode( GraphNode::POINT, gn1->m_parent, pc.a1 ); + gnt->m_connectDirectly = false; + + if( gn1->m_parent->IsConductive() ) + { + GraphConnection* gc = AddConnection( gn1, gnt ); + + if( gc ) + gc->m_path.m_show = false; + } + connect1 = gnt; + } + + if( gn2->m_parent->GetType() == CREEP_SHAPE::TYPE::POINT ) + { + connect2 = gn2; + } + else + { + GraphNode* gnt = AddNode( GraphNode::POINT, gn2->m_parent, pc.a2 ); + gnt->m_connectDirectly = false; + + if( gn2->m_parent->IsConductive() ) + { + GraphConnection* gc = AddConnection( gn2, gnt ); + + if( gc ) + gc->m_path.m_show = false; + } + connect2 = gnt; + } + AddConnection( connect1, connect2, pc ); + } + } + } +} + + +void CreepageGraph::Trim( double aWeightLimit ) +{ + std::vector<GraphConnection*> toRemove; + + // Collect connections to remove + for( auto& gc : m_connections ) + { + if( gc && ( gc->m_path.weight > aWeightLimit ) ) + { + toRemove.push_back( gc ); + } + } + + // Remove collected connections + for( const auto& gc : toRemove ) + { + RemoveConnection( gc ); + } +} + +void CreepageGraph::RemoveConnection( GraphConnection* aGc ) +{ + if( !aGc ) + return; + + for( GraphNode* gn : { aGc->n1, aGc->n2 } ) + { + if( gn ) + { + auto& nConns = gn->m_connections; + nConns.erase( std::remove( nConns.begin(), nConns.end(), aGc ), nConns.end() ); + + if( nConns.empty() ) + { + auto it = std::find_if( m_nodes.begin(), m_nodes.end(), + [&gn]( const GraphNode* node ) + { + return node == gn; + } ); + if( it != m_nodes.end() ) + { + m_nodes.erase( it ); + } + } + } + } + + + // Remove the connection from the graph's connections + m_connections.erase( std::remove( m_connections.begin(), m_connections.end(), aGc ), + m_connections.end() ); +} + + +GraphNode* CreepageGraph::AddNode( GraphNode::TYPE aType, CREEP_SHAPE* parent, VECTOR2I pos ) +{ + GraphNode* gn = FindNode( aType, parent, pos ); + if( gn ) + return gn; + + gn = new GraphNode( aType, parent, pos ); + m_nodes.push_back( gn ); + return gn; +} + +GraphNode* CreepageGraph::AddNodeVirtual() +{ + //Virtual nodes are always unique, do not try to find them + GraphNode* gn = new GraphNode( GraphNode::TYPE::VIRTUAL, nullptr ); + m_nodes.push_back( gn ); + return gn; +} + + +GraphConnection* CreepageGraph::AddConnection( GraphNode* aN1, GraphNode* aN2, path_connection aPc ) +{ + if( !aN1 || !aN2 ) + return nullptr; + + GraphConnection* gc = new GraphConnection( aN1, aN2, aPc ); + m_connections.push_back( gc ); + aN1->m_connections.push_back( gc ); + aN2->m_connections.push_back( gc ); + + return gc; +} + +GraphConnection* CreepageGraph::AddConnection( GraphNode* aN1, GraphNode* aN2 ) +{ + if( !aN1 || !aN2 ) + return nullptr; + + path_connection pc; + pc.a1 = aN1->m_pos; + pc.a2 = aN2->m_pos; + pc.weight = 0; + + return AddConnection( aN1, aN2, pc ); +} + +GraphNode* CreepageGraph::FindNode( GraphNode::TYPE aType, CREEP_SHAPE* aParent, VECTOR2I aPos ) +{ + for( GraphNode* gn : m_nodes ) + { + if( aPos == gn->m_pos && aParent == gn->m_parent && aType == gn->m_type ) + { + return gn; + } + } + return nullptr; +} + +/* + Physical creepage tests. + + Errors generated: + - DRCE_CREEPAGE + */ + +class DRC_TEST_PROVIDER_CREEPAGE : public DRC_TEST_PROVIDER_CLEARANCE_BASE +{ +public: + DRC_TEST_PROVIDER_CREEPAGE() : DRC_TEST_PROVIDER_CLEARANCE_BASE() {} + + virtual ~DRC_TEST_PROVIDER_CREEPAGE() {} + + virtual bool Run() override; + + virtual const wxString GetName() const override { return wxT( "creepage" ); }; + + virtual const wxString GetDescription() const override { return wxT( "Tests creepage" ); } + + double GetMaxConstraint( const std::vector<int>& aNetCodes ); + +private: + int testCreepage(); + int testCreepage( CreepageGraph& aGraph, int aNetCodeA, int aNetCodeB, PCB_LAYER_ID aLayer ); + +private: + DRC_RTREE m_itemTree; +}; + + +bool DRC_TEST_PROVIDER_CREEPAGE::Run() +{ + if( !ADVANCED_CFG::GetCfg().m_EnableCreepageDRC ) + { + return true; + } + + m_board = m_drcEngine->GetBoard(); + m_itemTree.clear(); + + //int errorMax = m_board->GetDesignSettings().m_MaxError; + + if( !m_drcEngine->IsErrorLimitExceeded( DRCE_CREEPAGE ) ) + { + if( !reportPhase( _( "Checking creepage..." ) ) ) + return false; // DRC cancelled + + testCreepage(); + } + return !m_drcEngine->IsCancelled(); +} + + +GraphNode* FindInGraphNodes( GraphNode* aNode, std::vector<GraphNode*>& aGraph ) +{ + if( !aNode ) + return nullptr; + + for( GraphNode* gn : aGraph ) + { + if( aNode->m_pos == gn->m_pos ) + { + return gn; + } + } + return nullptr; +} + +GraphNode* CreepageGraph::AddNetElements( int aNetCode, PCB_LAYER_ID aLayer, int aMaxCreepage ) +{ + GraphNode* virtualNode = AddNodeVirtual(); + virtualNode->m_net = aNetCode; + + for( FOOTPRINT* footprint : m_board.Footprints() ) + { + for( PAD* pad : footprint->Pads() ) + { + if( pad->GetNetCode() != aNetCode ) + continue; + + std::shared_ptr<SHAPE> padShape = pad->GetEffectiveShape( aLayer ); + + if( padShape ) + { + Addshape( *padShape, virtualNode, pad ); + } + } + } + + for( PCB_TRACK* track : m_board.Tracks() ) + { + if( !track ) + continue; + + if( track->GetNetCode() != aNetCode ) + continue; + + if( !track->IsOnLayer( aLayer ) ) + continue; + + if( track->GetEffectiveShape() == nullptr ) + continue; + + Addshape( *( track->GetEffectiveShape() ), virtualNode, track ); + } + + + for( ZONE* zone : m_board.Zones() ) + { + if( !zone ) + continue; + + if( zone->GetNetCode() != aNetCode ) + continue; + + if( zone->GetEffectiveShape( aLayer ) == nullptr ) + continue; + + Addshape( *( zone->GetEffectiveShape( aLayer ) ), virtualNode, zone ); + } + + const DRAWINGS drawings = m_board.Drawings(); + + for( BOARD_ITEM* drawing : drawings ) + { + if( !drawing ) + continue; + + if( !drawing->IsConnected() ) + continue; + + BOARD_CONNECTED_ITEM* bci = dynamic_cast<BOARD_CONNECTED_ITEM*>( drawing ); + + if( !bci ) + continue; + + if( bci->GetNetCode() != aNetCode ) + continue; + + if( bci->IsOnLayer( aLayer ) ) + { + Addshape( *( bci->GetEffectiveShape() ), virtualNode, bci ); + } + } + + + return virtualNode; +} + +int DRC_TEST_PROVIDER_CREEPAGE::testCreepage( CreepageGraph& aGraph, int aNetCodeA, int aNetCodeB, + PCB_LAYER_ID aLayer ) +{ + ZONE bci1( m_board ); + ZONE bci2( m_board ); + bci1.SetNetCode( aNetCodeA ); + bci2.SetNetCode( aNetCodeB ); + bci1.SetLayer( aLayer ); + bci2.SetLayer( aLayer ); + + + DRC_CONSTRAINT constraint; + constraint = m_drcEngine->EvalRules( CREEPAGE_CONSTRAINT, &bci1, &bci2, aLayer ); + double creepageValue = constraint.Value().Min(); + aGraph.SetTarget( creepageValue ); + + if( creepageValue <= 0 ) + return 0; + + // Let's make a quick "clearance test" + NETINFO_ITEM* netA = m_board->FindNet( aNetCodeA ); + NETINFO_ITEM* netB = m_board->FindNet( aNetCodeB ); + + if ( !netA || !netB ) + return 0; + + if ( netA->GetBoundingBox().Distance( netB->GetBoundingBox() ) > creepageValue ) + return 0; + + GraphNode* NetA = aGraph.AddNetElements( aNetCodeA, aLayer, creepageValue ); + GraphNode* NetB = aGraph.AddNetElements( aNetCodeB, aLayer, creepageValue ); + + + aGraph.GeneratePaths( creepageValue, aLayer ); + + std::vector<GraphNode*> nodes1 = aGraph.m_nodes; + std::vector<GraphNode*> nodes2 = aGraph.m_nodes; + + alg::for_all_pairs( aGraph.m_nodes.begin(), aGraph.m_nodes.end(), + [&]( GraphNode* aN1, GraphNode* aN2 ) + { + if( aN1 == aN2 ) + return; + + if( !aN1 || !aN2 ) + return; + + if( !( aN1->m_parent ) || !( aN2->m_parent ) ) + return; + + if( ( aN1->m_parent ) != ( aN2->m_parent ) ) + return; + + + if( aN1->m_parent->IsConductive() ) + return; + + if( aN1->m_connectDirectly || aN2->m_connectDirectly ) + return; + + // We are only looking for points on circles and arcs + + if( aN1->m_type != GraphNode::POINT ) + return; + + if( aN2->m_type != GraphNode::POINT ) + return; + + aN1->m_parent->ConnectChildren( aN1, aN2, aGraph ); + } ); + + std::vector<GraphConnection*> shortestPath; + shortestPath.clear(); + double distance = aGraph.Solve( NetA, NetB, shortestPath ); + + + if( ( shortestPath.size() > 0 ) && ( distance - creepageValue < 0 ) ) + { + std::shared_ptr<DRC_ITEM> drce = DRC_ITEM::Create( DRCE_CREEPAGE ); + wxString msg = formatMsg( _( "(%s creepage %s; actual %s)" ), constraint.GetName(), + creepageValue, distance ); + drce->SetErrorMessage( drce->GetErrorText() + wxS( " " ) + msg ); + drce->SetViolatingRule( constraint.GetParentRule() ); + + if( shortestPath.size() >= 4 && shortestPath[1]->n1 && shortestPath[1]->n2 ) + drce->SetItems( shortestPath[1]->n1->m_parent->GetParent(), + shortestPath[shortestPath.size() - 2]->n2->m_parent->GetParent() ); + + m_drcEngine->SetViolationHandler( + [&]( const std::shared_ptr<DRC_ITEM>& aItem, VECTOR2I aPos, int aReportLayer ) + { + PCB_MARKER* marker = new PCB_MARKER( aItem, aPos, aReportLayer ); + + if( !m_commit ) + return; + + std::vector<PCB_SHAPE> shapes; + + for( GraphConnection* gc : shortestPath ) + { + if( !gc ) + continue; + + for( PCB_SHAPE sh : gc->GetShapes() ) + shapes.push_back( sh ); + } + + marker->SetShapes( shapes ); + m_commit->Add( marker ); + } ); + + reportViolation( drce, shortestPath[1]->m_path.a2, aLayer ); + + } + + return 1; +} + +double DRC_TEST_PROVIDER_CREEPAGE::GetMaxConstraint( const std::vector<int>& aNetCodes ) +{ + double maxConstraint = 0; + DRC_CONSTRAINT constraint; + + ZONE bci1( m_board ); + ZONE bci2( m_board ); + + + alg::for_all_pairs( aNetCodes.begin(), aNetCodes.end(), + [&]( int aNet1, int aNet2 ) + { + if( aNet1 == aNet2 ) + return; + + bci1.SetNetCode( aNet1 ); + bci2.SetNetCode( aNet2 ); + + for( PCB_LAYER_ID layer : LSET::AllCuMask().CuStack() ) + { + bci1.SetLayer( layer ); + bci2.SetLayer( layer ); + constraint = m_drcEngine->EvalRules( CREEPAGE_CONSTRAINT, &bci1, + &bci2, layer ); + double value = constraint.Value().Min(); + maxConstraint = value > maxConstraint ? value : maxConstraint; + } + } ); + + return maxConstraint; +} + +int DRC_TEST_PROVIDER_CREEPAGE::testCreepage() +{ + if( !m_board ) + return -1; + + SHAPE_POLY_SET outline; + + if( !m_board->GetBoardPolygonOutlines( outline ) ) + return -1; + + const DRAWINGS drawings = m_board->Drawings(); + std::vector<BOARD_ITEM*> boardEdges; + CreepageGraph graph( *m_board ); + graph.m_boardOutline = &outline; + std::vector<CREEP_SHAPE*>* graphShapes = &( graph.m_shapeCollection ); + + // Add points + for( BOARD_ITEM* drawing : drawings ) + { + if( !drawing ) + continue; + + if( drawing->IsOnLayer( Edge_Cuts ) ) + { + boardEdges.push_back( drawing ); + } + } + + for( FOOTPRINT* fp : m_board->Footprints() ) + { + if( !fp ) + continue; + + for( BOARD_ITEM* drawing : fp->GraphicalItems() ) + { + if( !drawing ) + continue; + + if( drawing->IsOnLayer( Edge_Cuts ) ) + { + boardEdges.push_back( drawing ); + } + } + } + + for( const PAD* p : m_board->GetPads() ) + { + if( !p ) + continue; + + + if( p->GetAttribute() != PAD_ATTRIB::NPTH ) + continue; + + PCB_SHAPE* s = new PCB_SHAPE( NULL, SHAPE_T::CIRCLE ); + s->SetRadius( p->GetDrillSize().x / 2 ); + s->SetPosition( p->GetPosition() ); + boardEdges.push_back( s ); + } + + for( BOARD_ITEM* drawing : boardEdges ) + { + PCB_SHAPE* d = dynamic_cast<PCB_SHAPE*>( drawing ); + + if( !d ) + continue; + + switch( d->GetShape() ) + { + case SHAPE_T::SEGMENT: + { + BE_SHAPE_POINT* a = new BE_SHAPE_POINT( d->GetStart() ); + graphShapes->push_back( a ); + a = new BE_SHAPE_POINT( d->GetEnd() ); + graphShapes->push_back( a ); + break; + } + case SHAPE_T::RECTANGLE: + { + BE_SHAPE_POINT* a = new BE_SHAPE_POINT( d->GetStart() ); + graphShapes->push_back( a ); + a = new BE_SHAPE_POINT( d->GetEnd() ); + graphShapes->push_back( a ); + a = new BE_SHAPE_POINT( VECTOR2I( d->GetEnd().x, d->GetStart().y ) ); + graphShapes->push_back( a ); + a = new BE_SHAPE_POINT( VECTOR2I( d->GetStart().x, d->GetEnd().y ) ); + graphShapes->push_back( a ); + break; + } + case SHAPE_T::POLY: + { + std::vector<VECTOR2I> points; + d->DupPolyPointsList( points ); + + for ( auto p : points ) + { + BE_SHAPE_POINT* a = new BE_SHAPE_POINT( p ); + graphShapes->push_back( a ); + } + break; + } + case SHAPE_T::CIRCLE: + { + BE_SHAPE_CIRCLE* a = new BE_SHAPE_CIRCLE( d->GetCenter(), d->GetRadius() ); + a->SetParent( d ); + graphShapes->push_back( a ); + break; + } + + case SHAPE_T::ARC: + { + // If the arc is not locally convex, only use the endpoints + double tolerance = 10; + VECTOR2D center( double( d->GetCenter().x ), double( d->GetCenter().y ) ); + VECTOR2D mid( double( d->GetArcMid().x ), double( d->GetArcMid().y ) ); + VECTOR2D dir( mid - center ); + dir = dir / d->GetRadius() * ( d->GetRadius() - tolerance ); + + EDA_ANGLE alpha, beta; + d->CalcArcAngles( alpha, beta ); + BE_SHAPE_ARC* a = new BE_SHAPE_ARC( d->GetCenter(), d->GetRadius(), alpha, beta, + d->GetStart(), d->GetEnd() ); + a->SetParent( d ); + + graphShapes->push_back( a ); + break; + } + default: break; + } + } + + // Sort the vector + sort( graphShapes->begin(), graphShapes->end(), compareShapes ); + + // Use unique to identify the range of duplicates + auto uniqueEnd = unique( graphShapes->begin(), graphShapes->end(), areEquivalent ); + + // First, delete the duplicate elements + for( auto it = uniqueEnd; it != graphShapes->end(); ++it ) + { + delete *it; + } + + // Then, erase the elements from the vector + graphShapes->erase( uniqueEnd, graphShapes->end() ); + + // Finally, shrink the vector to free unused memory + graphShapes->shrink_to_fit(); + + + for( CREEP_SHAPE* p1 : *graphShapes ) + { + if( !p1 ) + continue; + + switch( p1->GetType() ) + { + case CREEP_SHAPE::TYPE::POINT: + graph.AddNode( GraphNode::TYPE::POINT, p1, p1->GetPos() ); + break; + case CREEP_SHAPE::TYPE::CIRCLE: + graph.AddNode( GraphNode::TYPE::CIRCLE, p1, p1->GetPos() ); + break; + case CREEP_SHAPE::TYPE::ARC: graph.AddNode( GraphNode::TYPE::ARC, p1, p1->GetPos() ); break; + default: break; + } + } + + graph.m_boardEdge = boardEdges; + + // Create a virtual node to connect all conductive element net / net group A + NETCODES_MAP nets = m_board->GetNetInfo().NetsByNetcode(); + + std::vector<int> netcodes; + + for( auto it = nets.begin(); it != nets.end(); it++ ) + { + netcodes.push_back( it->first ); + } + + int beNodeSize = graph.m_nodes.size(); + int beConnectionsSize = graph.m_connections.size(); + double maxConstraint = GetMaxConstraint( netcodes ); + + if( maxConstraint <= 0 ) + return 0; + + graph.GeneratePaths( maxConstraint, Edge_Cuts ); + + bool prevTestChangedGraph = false; + alg::for_all_pairs( netcodes.begin(), netcodes.end(), + [&]( int aNet1, int aNet2 ) + { + if( aNet1 == aNet2 ) + return; + + for( PCB_LAYER_ID layer : LSET::AllCuMask().CuStack() ) + { + if( !m_board->IsLayerEnabled( layer ) ) + continue; + + if ( prevTestChangedGraph ) + { + int vectorSize = graph.m_nodes.size(); + + for( int i = beNodeSize; i < vectorSize; i++ ) + { + delete graph.m_nodes[i]; + graph.m_nodes[i] = nullptr; + } + + graph.m_nodes.resize( beNodeSize ); + + vectorSize = graph.m_connections.size(); + + for( int i = beConnectionsSize; i < vectorSize; i++ ) + { + delete graph.m_connections[i]; + graph.m_connections[i] = nullptr; + } + + graph.m_connections.resize( beConnectionsSize ); + } + + prevTestChangedGraph = testCreepage( graph, aNet1, aNet2, layer ); + } + } ); + + return 1; +} + + +namespace detail +{ +static DRC_REGISTER_TEST_PROVIDER<DRC_TEST_PROVIDER_CREEPAGE> dummy; +} diff --git a/pcbnew/pcb_marker.cpp b/pcbnew/pcb_marker.cpp index ff007f0416..879b44f78e 100644 --- a/pcbnew/pcb_marker.cpp +++ b/pcbnew/pcb_marker.cpp @@ -348,7 +348,12 @@ void PCB_MARKER::SetZoom( double aZoomFactor ) const BOX2I PCB_MARKER::GetBoundingBox() const { - return GetBoundingBoxMarker(); + BOX2I box = GetBoundingBoxMarker(); + + for( auto& s : m_shapes ) + box.Merge( s.GetBoundingBox() ); + + return box; } diff --git a/pcbnew/pcb_marker.h b/pcbnew/pcb_marker.h index 7d4ef87504..de93ebdbdb 100644 --- a/pcbnew/pcb_marker.h +++ b/pcbnew/pcb_marker.h @@ -27,6 +27,7 @@ #include <board_item.h> +#include <pcb_shape.h> #include <rc_item.h> #include <marker_base.h> @@ -134,8 +135,14 @@ public: return wxT( "PCB_MARKER" ); } + std::vector<PCB_SHAPE> GetShapes() const { return m_shapes; }; + + void SetShapes( const std::vector<PCB_SHAPE>& aShapes ) { m_shapes = aShapes; }; + + protected: KIGFX::COLOR4D getColor() const override; + std::vector<PCB_SHAPE> m_shapes; }; #endif // PCB_MARKER_H diff --git a/pcbnew/pcb_painter.cpp b/pcbnew/pcb_painter.cpp index d8ba1b6ea0..99443ec744 100644 --- a/pcbnew/pcb_painter.cpp +++ b/pcbnew/pcb_painter.cpp @@ -2943,6 +2943,31 @@ void PCB_PAINTER::draw( const PCB_MARKER* aMarker, int aLayer ) m_gal->DrawPolygon( polygon ); m_gal->Restore(); + + if( isShadow || ( aMarker->GetShapes().size() <= 0 ) ) + return; // Don't add shadow to shapes + + // Show pat + m_gal->SetIsFill( false ); + m_gal->SetIsStroke( true ); + m_gal->SetStrokeColor( color ); + m_gal->SetLineWidth( aMarker->MarkerScale() ); + + for( auto& shape : aMarker->GetShapes() ) + { + switch( shape.GetShape() ) + { + case SHAPE_T::SEGMENT: m_gal->DrawSegment( shape.GetStart(), shape.GetEnd(), 0 ); break; + case SHAPE_T::ARC: + { + EDA_ANGLE startAngle, endAngle; + shape.CalcArcAngles( startAngle, endAngle ); + m_gal->DrawArc( shape.GetCenter(), shape.GetRadius(), startAngle, shape.GetArcAngle() ); + break; + } + default: break; + } + } } diff --git a/pcbnew/tools/drc_tool.cpp b/pcbnew/tools/drc_tool.cpp index abcb7cf657..ea0bfacb19 100644 --- a/pcbnew/tools/drc_tool.cpp +++ b/pcbnew/tools/drc_tool.cpp @@ -178,7 +178,8 @@ void DRC_TOOL::RunTests( PROGRESS_REPORTER* aProgressReporter, bool aRefillZones commit.Add( marker ); } ); - m_drcEngine->RunTests( m_editFrame->GetUserUnits(), aReportAllTrackErrors, aTestFootprints ); + m_drcEngine->RunTests( m_editFrame->GetUserUnits(), aReportAllTrackErrors, aTestFootprints, + &commit ); m_drcEngine->SetProgressReporter( nullptr ); m_drcEngine->ClearViolationHandler();