You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1119 lines
35 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2019-2023 KiCad Developers, see AUTHORS.txt for contributors.
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU General Public License
  8. * as published by the Free Software Foundation; either version 2
  9. * of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program; if not, you may find one here:
  18. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  19. * or you may search the http://www.gnu.org website for the version 2 license,
  20. * or you may write to the Free Software Foundation, Inc.,
  21. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  22. */
  23. #include <cstdio>
  24. #include <memory>
  25. #include <wx/log.h>
  26. #include <board.h>
  27. #include <board_design_settings.h>
  28. #include <drc/drc_rtree.h>
  29. #include <drc/drc_engine.h>
  30. #include <pcb_track.h>
  31. #include <pcb_group.h>
  32. #include <geometry/shape_segment.h>
  33. #include <pcbexpr_evaluator.h>
  34. #include <connectivity/connectivity_data.h>
  35. #include <connectivity/connectivity_algo.h>
  36. #include <connectivity/from_to_cache.h>
  37. bool fromToFunc( LIBEVAL::CONTEXT* aCtx, void* self )
  38. {
  39. PCBEXPR_VAR_REF* vref = static_cast<PCBEXPR_VAR_REF*>( self );
  40. BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr;
  41. LIBEVAL::VALUE* result = aCtx->AllocValue();
  42. LIBEVAL::VALUE* argTo = aCtx->Pop();
  43. LIBEVAL::VALUE* argFrom = aCtx->Pop();
  44. result->Set(0.0);
  45. aCtx->Push( result );
  46. if(!item)
  47. return false;
  48. auto ftCache = item->GetBoard()->GetConnectivity()->GetFromToCache();
  49. if( !ftCache )
  50. {
  51. wxLogWarning( wxT( "Attempting to call fromTo() with non-existent from-to cache." ) );
  52. return true;
  53. }
  54. if( ftCache->IsOnFromToPath( static_cast<BOARD_CONNECTED_ITEM*>( item ),
  55. argFrom->AsString(), argTo->AsString() ) )
  56. {
  57. result->Set(1.0);
  58. }
  59. return true;
  60. }
  61. #define MISSING_LAYER_ARG( f ) wxString::Format( _( "Missing layer name argument to %s." ), f )
  62. static void existsOnLayerFunc( LIBEVAL::CONTEXT* aCtx, void *self )
  63. {
  64. PCBEXPR_VAR_REF* vref = static_cast<PCBEXPR_VAR_REF*>( self );
  65. BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr;
  66. LIBEVAL::VALUE* arg = aCtx->Pop();
  67. LIBEVAL::VALUE* result = aCtx->AllocValue();
  68. result->Set( 0.0 );
  69. aCtx->Push( result );
  70. if( !item )
  71. return;
  72. if( !arg || arg->AsString().IsEmpty() )
  73. {
  74. if( aCtx->HasErrorCallback() )
  75. aCtx->ReportError( MISSING_LAYER_ARG( wxT( "existsOnLayer()" ) ) );
  76. return;
  77. }
  78. result->SetDeferredEval(
  79. [item, arg, aCtx]() -> double
  80. {
  81. const wxString& layerName = arg->AsString();
  82. wxPGChoices& layerMap = ENUM_MAP<PCB_LAYER_ID>::Instance().Choices();
  83. if( aCtx->HasErrorCallback())
  84. {
  85. /*
  86. * Interpreted version
  87. */
  88. bool anyMatch = false;
  89. for( unsigned ii = 0; ii < layerMap.GetCount(); ++ii )
  90. {
  91. wxPGChoiceEntry& entry = layerMap[ ii ];
  92. if( entry.GetText().Matches( layerName ))
  93. {
  94. anyMatch = true;
  95. if( item->IsOnLayer( ToLAYER_ID( entry.GetValue() ) ) )
  96. return 1.0;
  97. }
  98. }
  99. if( !anyMatch )
  100. {
  101. aCtx->ReportError( wxString::Format( _( "Unrecognized layer '%s'" ),
  102. layerName ) );
  103. }
  104. }
  105. else
  106. {
  107. /*
  108. * Compiled version
  109. */
  110. BOARD* board = item->GetBoard();
  111. std::unique_lock<std::mutex> cacheLock( board->m_CachesMutex );
  112. auto i = board->m_LayerExpressionCache.find( layerName );
  113. LSET mask;
  114. if( i == board->m_LayerExpressionCache.end() )
  115. {
  116. for( unsigned ii = 0; ii < layerMap.GetCount(); ++ii )
  117. {
  118. wxPGChoiceEntry& entry = layerMap[ ii ];
  119. if( entry.GetText().Matches( layerName ) )
  120. mask.set( ToLAYER_ID( entry.GetValue() ) );
  121. }
  122. board->m_LayerExpressionCache[ layerName ] = mask;
  123. }
  124. else
  125. {
  126. mask = i->second;
  127. }
  128. if( ( item->GetLayerSet() & mask ).any() )
  129. return 1.0;
  130. }
  131. return 0.0;
  132. } );
  133. }
  134. static void isPlatedFunc( LIBEVAL::CONTEXT* aCtx, void* self )
  135. {
  136. LIBEVAL::VALUE* result = aCtx->AllocValue();
  137. result->Set( 0.0 );
  138. aCtx->Push( result );
  139. PCBEXPR_VAR_REF* vref = static_cast<PCBEXPR_VAR_REF*>( self );
  140. BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr;
  141. if( !item )
  142. return;
  143. if( item->Type() == PCB_PAD_T && static_cast<PAD*>( item )->GetAttribute() == PAD_ATTRIB::PTH )
  144. result->Set( 1.0 );
  145. else if( item->Type() == PCB_VIA_T )
  146. result->Set( 1.0 );
  147. }
  148. bool collidesWithCourtyard( BOARD_ITEM* aItem, std::shared_ptr<SHAPE>& aItemShape,
  149. PCBEXPR_CONTEXT* aCtx, FOOTPRINT* aFootprint, PCB_LAYER_ID aSide )
  150. {
  151. SHAPE_POLY_SET footprintCourtyard;
  152. footprintCourtyard = aFootprint->GetCourtyard( aSide );
  153. if( !aItemShape )
  154. {
  155. // Since rules are used for zone filling we can't rely on the filled shapes.
  156. // Use the zone outline instead.
  157. if( ZONE* zone = dynamic_cast<ZONE*>( aItem ) )
  158. aItemShape.reset( zone->Outline()->Clone() );
  159. else
  160. aItemShape = aItem->GetEffectiveShape( aCtx->GetLayer() );
  161. }
  162. return footprintCourtyard.Collide( aItemShape.get() );
  163. };
  164. static bool searchFootprints( BOARD* aBoard, const wxString& aArg, PCBEXPR_CONTEXT* aCtx,
  165. const std::function<bool( FOOTPRINT* )>& aFunc )
  166. {
  167. if( aArg == wxT( "A" ) )
  168. {
  169. FOOTPRINT* fp = dynamic_cast<FOOTPRINT*>( aCtx->GetItem( 0 ) );
  170. if( fp && aFunc( fp ) )
  171. return true;
  172. }
  173. else if( aArg == wxT( "B" ) )
  174. {
  175. FOOTPRINT* fp = dynamic_cast<FOOTPRINT*>( aCtx->GetItem( 1 ) );
  176. if( fp && aFunc( fp ) )
  177. return true;
  178. }
  179. else for( FOOTPRINT* fp : aBoard->Footprints() )
  180. {
  181. if( fp->GetReference().Matches( aArg ) )
  182. {
  183. if( aFunc( fp ) )
  184. return true;
  185. }
  186. }
  187. return false;
  188. }
  189. #define MISSING_FP_ARG( f ) \
  190. wxString::Format( _( "Missing footprint argument (A, B, or reference designator) to %s." ), f )
  191. static void intersectsCourtyardFunc( LIBEVAL::CONTEXT* aCtx, void* self )
  192. {
  193. PCBEXPR_CONTEXT* context = static_cast<PCBEXPR_CONTEXT*>( aCtx );
  194. LIBEVAL::VALUE* arg = context->Pop();
  195. LIBEVAL::VALUE* result = context->AllocValue();
  196. result->Set( 0.0 );
  197. context->Push( result );
  198. if( !arg || arg->AsString().IsEmpty() )
  199. {
  200. if( context->HasErrorCallback() )
  201. context->ReportError( MISSING_FP_ARG( wxT( "intersectsCourtyard()" ) ) );
  202. return;
  203. }
  204. PCBEXPR_VAR_REF* vref = static_cast<PCBEXPR_VAR_REF*>( self );
  205. BOARD_ITEM* item = vref ? vref->GetObject( context ) : nullptr;
  206. if( !item )
  207. return;
  208. result->SetDeferredEval(
  209. [item, arg, context]() -> double
  210. {
  211. BOARD* board = item->GetBoard();
  212. std::shared_ptr<SHAPE> itemShape;
  213. if( searchFootprints( board, arg->AsString(), context,
  214. [&]( FOOTPRINT* fp )
  215. {
  216. PTR_PTR_CACHE_KEY key = { fp, item };
  217. std::unique_lock<std::mutex> cacheLock( board->m_CachesMutex );
  218. if( ( item->GetFlags() & ROUTER_TRANSIENT ) == 0 )
  219. {
  220. auto i = board->m_IntersectsCourtyardCache.find( key );
  221. if( i != board->m_IntersectsCourtyardCache.end() )
  222. return i->second;
  223. }
  224. bool res = collidesWithCourtyard( item, itemShape, context, fp, F_Cu )
  225. || collidesWithCourtyard( item, itemShape, context, fp, B_Cu );
  226. if( ( item->GetFlags() & ROUTER_TRANSIENT ) == 0 )
  227. board->m_IntersectsCourtyardCache[ key ] = res;
  228. return res;
  229. } ) )
  230. {
  231. return 1.0;
  232. }
  233. return 0.0;
  234. } );
  235. }
  236. static void intersectsFrontCourtyardFunc( LIBEVAL::CONTEXT* aCtx, void* self )
  237. {
  238. PCBEXPR_CONTEXT* context = static_cast<PCBEXPR_CONTEXT*>( aCtx );
  239. LIBEVAL::VALUE* arg = context->Pop();
  240. LIBEVAL::VALUE* result = context->AllocValue();
  241. result->Set( 0.0 );
  242. context->Push( result );
  243. if( !arg || arg->AsString().IsEmpty() )
  244. {
  245. if( context->HasErrorCallback() )
  246. context->ReportError( MISSING_FP_ARG( wxT( "intersectsFrontCourtyard()" ) ) );
  247. return;
  248. }
  249. PCBEXPR_VAR_REF* vref = static_cast<PCBEXPR_VAR_REF*>( self );
  250. BOARD_ITEM* item = vref ? vref->GetObject( context ) : nullptr;
  251. if( !item )
  252. return;
  253. result->SetDeferredEval(
  254. [item, arg, context]() -> double
  255. {
  256. BOARD* board = item->GetBoard();
  257. std::shared_ptr<SHAPE> itemShape;
  258. if( searchFootprints( board, arg->AsString(), context,
  259. [&]( FOOTPRINT* fp )
  260. {
  261. PTR_PTR_CACHE_KEY key = { fp, item };
  262. std::unique_lock<std::mutex> cacheLock( board->m_CachesMutex );
  263. if( ( item->GetFlags() & ROUTER_TRANSIENT ) == 0 )
  264. {
  265. auto i = board->m_IntersectsFCourtyardCache.find( key );
  266. if( i != board->m_IntersectsFCourtyardCache.end() )
  267. return i->second;
  268. }
  269. bool res = collidesWithCourtyard( item, itemShape, context, fp, F_Cu );
  270. if( ( item->GetFlags() & ROUTER_TRANSIENT ) == 0 )
  271. board->m_IntersectsFCourtyardCache[ key ] = res;
  272. return res;
  273. } ) )
  274. {
  275. return 1.0;
  276. }
  277. return 0.0;
  278. } );
  279. }
  280. static void intersectsBackCourtyardFunc( LIBEVAL::CONTEXT* aCtx, void* self )
  281. {
  282. PCBEXPR_CONTEXT* context = static_cast<PCBEXPR_CONTEXT*>( aCtx );
  283. LIBEVAL::VALUE* arg = context->Pop();
  284. LIBEVAL::VALUE* result = context->AllocValue();
  285. result->Set( 0.0 );
  286. context->Push( result );
  287. if( !arg || arg->AsString().IsEmpty() )
  288. {
  289. if( context->HasErrorCallback() )
  290. context->ReportError( MISSING_FP_ARG( wxT( "intersectsBackCourtyard()" ) ) );
  291. return;
  292. }
  293. PCBEXPR_VAR_REF* vref = static_cast<PCBEXPR_VAR_REF*>( self );
  294. BOARD_ITEM* item = vref ? vref->GetObject( context ) : nullptr;
  295. if( !item )
  296. return;
  297. result->SetDeferredEval(
  298. [item, arg, context]() -> double
  299. {
  300. BOARD* board = item->GetBoard();
  301. std::shared_ptr<SHAPE> itemShape;
  302. if( searchFootprints( board, arg->AsString(), context,
  303. [&]( FOOTPRINT* fp )
  304. {
  305. PTR_PTR_CACHE_KEY key = { fp, item };
  306. std::unique_lock<std::mutex> cacheLock( board->m_CachesMutex );
  307. if( ( item->GetFlags() & ROUTER_TRANSIENT ) == 0 )
  308. {
  309. auto i = board->m_IntersectsBCourtyardCache.find( key );
  310. if( i != board->m_IntersectsBCourtyardCache.end() )
  311. return i->second;
  312. }
  313. bool res = collidesWithCourtyard( item, itemShape, context, fp, B_Cu );
  314. if( ( item->GetFlags() & ROUTER_TRANSIENT ) == 0 )
  315. board->m_IntersectsBCourtyardCache[ key ] = res;
  316. return res;
  317. } ) )
  318. {
  319. return 1.0;
  320. }
  321. return 0.0;
  322. } );
  323. }
  324. bool collidesWithArea( BOARD_ITEM* aItem, PCBEXPR_CONTEXT* aCtx, ZONE* aArea )
  325. {
  326. BOARD* board = aArea->GetBoard();
  327. BOX2I areaBBox = aArea->GetBoundingBox();
  328. std::shared_ptr<SHAPE> shape;
  329. // Collisions include touching, so we need to deflate outline by enough to exclude it.
  330. // This is particularly important for detecting copper fills as they will be exactly
  331. // touching along the entire exclusion border.
  332. SHAPE_POLY_SET areaOutline = aArea->Outline()->CloneDropTriangulation();
  333. areaOutline.ClearArcs();
  334. areaOutline.Deflate( board->GetDesignSettings().GetDRCEpsilon(),
  335. CORNER_STRATEGY::ALLOW_ACUTE_CORNERS, ARC_LOW_DEF );
  336. if( aItem->GetFlags() & HOLE_PROXY )
  337. {
  338. if( aItem->Type() == PCB_PAD_T )
  339. {
  340. return areaOutline.Collide( aItem->GetEffectiveHoleShape().get() );
  341. }
  342. else if( aItem->Type() == PCB_VIA_T )
  343. {
  344. LSET overlap = aItem->GetLayerSet() & aArea->GetLayerSet();
  345. /// Avoid buried vias that don't overlap the zone's layers
  346. if( overlap.any() )
  347. {
  348. if( aCtx->GetLayer() == UNDEFINED_LAYER || overlap.Contains( aCtx->GetLayer() ) )
  349. return areaOutline.Collide( aItem->GetEffectiveHoleShape().get() );
  350. }
  351. }
  352. return false;
  353. }
  354. if( aItem->Type() == PCB_FOOTPRINT_T )
  355. {
  356. FOOTPRINT* footprint = static_cast<FOOTPRINT*>( aItem );
  357. if( ( footprint->GetFlags() & MALFORMED_COURTYARDS ) != 0 )
  358. {
  359. if( aCtx->HasErrorCallback() )
  360. aCtx->ReportError( _( "Footprint's courtyard is not a single, closed shape." ) );
  361. return false;
  362. }
  363. if( ( aArea->GetLayerSet() & LSET::FrontMask() ).any() )
  364. {
  365. const SHAPE_POLY_SET& courtyard = footprint->GetCourtyard( F_CrtYd );
  366. if( courtyard.OutlineCount() == 0 )
  367. {
  368. if( aCtx->HasErrorCallback() )
  369. aCtx->ReportError( _( "Footprint has no front courtyard." ) );
  370. }
  371. else if( areaOutline.Collide( &courtyard.Outline( 0 ) ) )
  372. {
  373. return true;
  374. }
  375. }
  376. if( ( aArea->GetLayerSet() & LSET::BackMask() ).any() )
  377. {
  378. const SHAPE_POLY_SET& courtyard = footprint->GetCourtyard( B_CrtYd );
  379. if( courtyard.OutlineCount() == 0 )
  380. {
  381. if( aCtx->HasErrorCallback() )
  382. aCtx->ReportError( _( "Footprint has no back courtyard." ) );
  383. }
  384. else if( areaOutline.Collide( &courtyard.Outline( 0 ) ) )
  385. {
  386. return true;
  387. }
  388. }
  389. return false;
  390. }
  391. if( aItem->Type() == PCB_ZONE_T )
  392. {
  393. ZONE* zone = static_cast<ZONE*>( aItem );
  394. if( !zone->IsFilled() )
  395. return false;
  396. DRC_RTREE* zoneRTree = board->m_CopperZoneRTreeCache[ zone ].get();
  397. if( zoneRTree )
  398. {
  399. for( PCB_LAYER_ID layer : aArea->GetLayerSet().Seq() )
  400. {
  401. if( aCtx->GetLayer() == layer || aCtx->GetLayer() == UNDEFINED_LAYER )
  402. {
  403. if( zoneRTree->QueryColliding( areaBBox, &areaOutline, layer ) )
  404. return true;
  405. }
  406. }
  407. }
  408. return false;
  409. }
  410. else
  411. {
  412. PCB_LAYER_ID layer = aCtx->GetLayer();
  413. if( layer != UNDEFINED_LAYER && !( aArea->GetLayerSet().Contains( layer ) ) )
  414. return false;
  415. if( !shape )
  416. shape = aItem->GetEffectiveShape( layer );
  417. return areaOutline.Collide( shape.get() );
  418. }
  419. }
  420. bool searchAreas( BOARD* aBoard, const wxString& aArg, PCBEXPR_CONTEXT* aCtx,
  421. const std::function<bool( ZONE* )>& aFunc )
  422. {
  423. if( aArg == wxT( "A" ) )
  424. {
  425. return aFunc( dynamic_cast<ZONE*>( aCtx->GetItem( 0 ) ) );
  426. }
  427. else if( aArg == wxT( "B" ) )
  428. {
  429. return aFunc( dynamic_cast<ZONE*>( aCtx->GetItem( 1 ) ) );
  430. }
  431. else if( KIID::SniffTest( aArg ) )
  432. {
  433. KIID target( aArg );
  434. for( ZONE* area : aBoard->Zones() )
  435. {
  436. // Only a single zone can match the UUID; exit once we find a match whether
  437. // "inside" or not
  438. if( area->m_Uuid == target )
  439. return aFunc( area );
  440. }
  441. for( FOOTPRINT* footprint : aBoard->Footprints() )
  442. {
  443. for( ZONE* area : footprint->Zones() )
  444. {
  445. // Only a single zone can match the UUID; exit once we find a match
  446. // whether "inside" or not
  447. if( area->m_Uuid == target )
  448. return aFunc( area );
  449. }
  450. }
  451. return false;
  452. }
  453. else // Match on zone name
  454. {
  455. for( ZONE* area : aBoard->Zones() )
  456. {
  457. if( area->GetZoneName().Matches( aArg ) )
  458. {
  459. // Many zones can match the name; exit only when we find an "inside"
  460. if( aFunc( area ) )
  461. return true;
  462. }
  463. }
  464. for( FOOTPRINT* footprint : aBoard->Footprints() )
  465. {
  466. for( ZONE* area : footprint->Zones() )
  467. {
  468. // Many zones can match the name; exit only when we find an "inside"
  469. if( area->GetZoneName().Matches( aArg ) )
  470. {
  471. if( aFunc( area ) )
  472. return true;
  473. }
  474. }
  475. }
  476. return false;
  477. }
  478. }
  479. #define MISSING_AREA_ARG( f ) \
  480. wxString::Format( _( "Missing rule-area argument (A, B, or rule-area name) to %s." ), f )
  481. static void intersectsAreaFunc( LIBEVAL::CONTEXT* aCtx, void* self )
  482. {
  483. PCBEXPR_CONTEXT* context = static_cast<PCBEXPR_CONTEXT*>( aCtx );
  484. LIBEVAL::VALUE* arg = aCtx->Pop();
  485. LIBEVAL::VALUE* result = aCtx->AllocValue();
  486. result->Set( 0.0 );
  487. aCtx->Push( result );
  488. if( !arg || arg->AsString().IsEmpty() )
  489. {
  490. if( aCtx->HasErrorCallback() )
  491. aCtx->ReportError( MISSING_AREA_ARG( wxT( "intersectsArea()" ) ) );
  492. return;
  493. }
  494. PCBEXPR_VAR_REF* vref = static_cast<PCBEXPR_VAR_REF*>( self );
  495. BOARD_ITEM* item = vref ? vref->GetObject( context ) : nullptr;
  496. if( !item )
  497. return;
  498. result->SetDeferredEval(
  499. [item, arg, context]() -> double
  500. {
  501. BOARD* board = item->GetBoard();
  502. PCB_LAYER_ID aLayer = context->GetLayer();
  503. BOX2I itemBBox = item->GetBoundingBox();
  504. if( searchAreas( board, arg->AsString(), context,
  505. [&]( ZONE* aArea )
  506. {
  507. if( !aArea || aArea == item || aArea->GetParent() == item )
  508. return false;
  509. LSET commonLayers = aArea->GetLayerSet() & item->GetLayerSet();
  510. if( !commonLayers.any() )
  511. return false;
  512. if( !aArea->GetBoundingBox().Intersects( itemBBox ) )
  513. return false;
  514. std::unique_lock<std::mutex> cacheLock( board->m_CachesMutex );
  515. LSET testLayers;
  516. PTR_PTR_LAYER_CACHE_KEY key;
  517. if( aLayer != UNDEFINED_LAYER )
  518. testLayers.set( aLayer );
  519. else
  520. testLayers = commonLayers;
  521. for( PCB_LAYER_ID layer : testLayers.UIOrder() )
  522. {
  523. if( ( item->GetFlags() & ROUTER_TRANSIENT ) == 0 )
  524. {
  525. key = { aArea, item, layer };
  526. auto i = board->m_IntersectsAreaCache.find( key );
  527. if( i != board->m_IntersectsAreaCache.end() && i->second )
  528. return true;
  529. }
  530. bool collides = collidesWithArea( item, context, aArea );
  531. if( ( item->GetFlags() & ROUTER_TRANSIENT ) == 0 )
  532. board->m_IntersectsAreaCache[ key ] = collides;
  533. if( collides )
  534. return true;
  535. }
  536. return false;
  537. } ) )
  538. {
  539. return 1.0;
  540. }
  541. return 0.0;
  542. } );
  543. }
  544. static void enclosedByAreaFunc( LIBEVAL::CONTEXT* aCtx, void* self )
  545. {
  546. PCBEXPR_CONTEXT* context = static_cast<PCBEXPR_CONTEXT*>( aCtx );
  547. LIBEVAL::VALUE* arg = aCtx->Pop();
  548. LIBEVAL::VALUE* result = aCtx->AllocValue();
  549. result->Set( 0.0 );
  550. aCtx->Push( result );
  551. if( !arg || arg->AsString().IsEmpty() )
  552. {
  553. if( aCtx->HasErrorCallback() )
  554. aCtx->ReportError( MISSING_AREA_ARG( wxT( "enclosedByArea()" ) ) );
  555. return;
  556. }
  557. PCBEXPR_VAR_REF* vref = static_cast<PCBEXPR_VAR_REF*>( self );
  558. BOARD_ITEM* item = vref ? vref->GetObject( context ) : nullptr;
  559. if( !item )
  560. return;
  561. result->SetDeferredEval(
  562. [item, arg, context]() -> double
  563. {
  564. BOARD* board = item->GetBoard();
  565. int maxError = board->GetDesignSettings().m_MaxError;
  566. PCB_LAYER_ID layer = context->GetLayer();
  567. BOX2I itemBBox = item->GetBoundingBox();
  568. if( searchAreas( board, arg->AsString(), context,
  569. [&]( ZONE* aArea )
  570. {
  571. if( !aArea || aArea == item || aArea->GetParent() == item )
  572. return false;
  573. if( !( aArea->GetLayerSet() & item->GetLayerSet() ).any() )
  574. return false;
  575. if( !aArea->GetBoundingBox().Intersects( itemBBox ) )
  576. return false;
  577. std::unique_lock<std::mutex> cacheLock( board->m_CachesMutex );
  578. PTR_PTR_LAYER_CACHE_KEY key = { aArea, item, layer };
  579. if( ( item->GetFlags() & ROUTER_TRANSIENT ) == 0 )
  580. {
  581. auto i = board->m_EnclosedByAreaCache.find( key );
  582. if( i != board->m_EnclosedByAreaCache.end() )
  583. return i->second;
  584. }
  585. SHAPE_POLY_SET itemShape;
  586. bool enclosedByArea;
  587. item->TransformShapeToPolygon( itemShape, layer, 0, maxError,
  588. ERROR_OUTSIDE );
  589. if( itemShape.IsEmpty() )
  590. {
  591. // If it's already empty then our test will have no meaning.
  592. enclosedByArea = false;
  593. }
  594. else
  595. {
  596. itemShape.BooleanSubtract( *aArea->Outline(),
  597. SHAPE_POLY_SET::PM_FAST );
  598. enclosedByArea = itemShape.IsEmpty();
  599. }
  600. if( ( item->GetFlags() & ROUTER_TRANSIENT ) == 0 )
  601. board->m_EnclosedByAreaCache[ key ] = enclosedByArea;
  602. return enclosedByArea;
  603. } ) )
  604. {
  605. return 1.0;
  606. }
  607. return 0.0;
  608. } );
  609. }
  610. #define MISSING_GROUP_ARG( f ) \
  611. wxString::Format( _( "Missing group name argument to %s." ), f )
  612. static void memberOfGroupFunc( LIBEVAL::CONTEXT* aCtx, void* self )
  613. {
  614. LIBEVAL::VALUE* arg = aCtx->Pop();
  615. LIBEVAL::VALUE* result = aCtx->AllocValue();
  616. result->Set( 0.0 );
  617. aCtx->Push( result );
  618. if( !arg || arg->AsString().IsEmpty() )
  619. {
  620. if( aCtx->HasErrorCallback() )
  621. aCtx->ReportError( MISSING_GROUP_ARG( wxT( "memberOfGroup()" ) ) );
  622. return;
  623. }
  624. PCBEXPR_VAR_REF* vref = static_cast<PCBEXPR_VAR_REF*>( self );
  625. BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr;
  626. if( !item )
  627. return;
  628. result->SetDeferredEval(
  629. [item, arg]() -> double
  630. {
  631. PCB_GROUP* group = item->GetParentGroup();
  632. if( !group && item->GetParent() && item->GetParent()->Type() == PCB_FOOTPRINT_T )
  633. group = item->GetParent()->GetParentGroup();
  634. while( group )
  635. {
  636. if( group->GetName().Matches( arg->AsString() ) )
  637. return 1.0;
  638. group = group->GetParentGroup();
  639. }
  640. return 0.0;
  641. } );
  642. }
  643. #define MISSING_SHEET_ARG( f ) \
  644. wxString::Format( _( "Missing sheet name argument to %s." ), f )
  645. static void memberOfSheetFunc( LIBEVAL::CONTEXT* aCtx, void* self )
  646. {
  647. LIBEVAL::VALUE* arg = aCtx->Pop();
  648. LIBEVAL::VALUE* result = aCtx->AllocValue();
  649. result->Set( 0.0 );
  650. aCtx->Push( result );
  651. if( !arg || arg->AsString().IsEmpty() )
  652. {
  653. if( aCtx->HasErrorCallback() )
  654. aCtx->ReportError( MISSING_SHEET_ARG( wxT( "memberOfSheet()" ) ) );
  655. return;
  656. }
  657. PCBEXPR_VAR_REF* vref = static_cast<PCBEXPR_VAR_REF*>( self );
  658. BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr;
  659. if( !item )
  660. return;
  661. result->SetDeferredEval(
  662. [item, arg]() -> double
  663. {
  664. FOOTPRINT* fp = item->GetParentFootprint();
  665. if( !fp )
  666. return 0.0;
  667. if( fp->GetSheetname().Matches( arg->AsString() ) )
  668. return 1.0;
  669. if( ( arg->AsString().Matches( wxT( "/" ) ) || arg->AsString().IsEmpty() )
  670. && fp->GetSheetname().IsEmpty() )
  671. {
  672. return 1.0;
  673. }
  674. return 0.0;
  675. } );
  676. }
  677. #define MISSING_REF_ARG( f ) \
  678. wxString::Format( _( "Missing footprint argument (reference designator) to %s." ), f )
  679. static void memberOfFootprintFunc( LIBEVAL::CONTEXT* aCtx, void* self )
  680. {
  681. LIBEVAL::VALUE* arg = aCtx->Pop();
  682. LIBEVAL::VALUE* result = aCtx->AllocValue();
  683. result->Set( 0.0 );
  684. aCtx->Push( result );
  685. if( !arg || arg->AsString().IsEmpty() )
  686. {
  687. if( aCtx->HasErrorCallback() )
  688. aCtx->ReportError( MISSING_REF_ARG( wxT( "memberOfFootprint()" ) ) );
  689. return;
  690. }
  691. PCBEXPR_VAR_REF* vref = static_cast<PCBEXPR_VAR_REF*>( self );
  692. BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr;
  693. if( !item )
  694. return;
  695. result->SetDeferredEval(
  696. [item, arg]() -> double
  697. {
  698. ;
  699. if( FOOTPRINT* parentFP = item->GetParentFootprint() )
  700. {
  701. if( parentFP->GetReference().Matches( arg->AsString() ) )
  702. return 1.0;
  703. }
  704. return 0.0;
  705. } );
  706. }
  707. static void isMicroVia( LIBEVAL::CONTEXT* aCtx, void* self )
  708. {
  709. PCBEXPR_VAR_REF* vref = static_cast<PCBEXPR_VAR_REF*>( self );
  710. BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr;
  711. LIBEVAL::VALUE* result = aCtx->AllocValue();
  712. result->Set( 0.0 );
  713. aCtx->Push( result );
  714. if( item && item->Type() == PCB_VIA_T
  715. && static_cast<PCB_VIA*>( item )->GetViaType() == VIATYPE::MICROVIA )
  716. {
  717. result->Set ( 1.0 );
  718. }
  719. }
  720. static void isBlindBuriedViaFunc( LIBEVAL::CONTEXT* aCtx, void* self )
  721. {
  722. PCBEXPR_VAR_REF* vref = static_cast<PCBEXPR_VAR_REF*>( self );
  723. BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr;
  724. LIBEVAL::VALUE* result = aCtx->AllocValue();
  725. result->Set( 0.0 );
  726. aCtx->Push( result );
  727. if( item && item->Type() == PCB_VIA_T
  728. && static_cast<PCB_VIA*>( item )->GetViaType() == VIATYPE::MICROVIA )
  729. {
  730. result->Set ( 1.0 );
  731. }
  732. }
  733. static void isCoupledDiffPairFunc( LIBEVAL::CONTEXT* aCtx, void* self )
  734. {
  735. PCBEXPR_CONTEXT* context = static_cast<PCBEXPR_CONTEXT*>( aCtx );
  736. BOARD_CONNECTED_ITEM* a = dynamic_cast<BOARD_CONNECTED_ITEM*>( context->GetItem( 0 ) );
  737. BOARD_CONNECTED_ITEM* b = dynamic_cast<BOARD_CONNECTED_ITEM*>( context->GetItem( 1 ) );
  738. LIBEVAL::VALUE* result = aCtx->AllocValue();
  739. result->Set( 0.0 );
  740. aCtx->Push( result );
  741. result->SetDeferredEval(
  742. [a, b, context]() -> double
  743. {
  744. NETINFO_ITEM* netinfo = a ? a->GetNet() : nullptr;
  745. if( !netinfo )
  746. return 0.0;
  747. wxString coupledNet;
  748. wxString dummy;
  749. if( !DRC_ENGINE::MatchDpSuffix( netinfo->GetNetname(), coupledNet, dummy ) )
  750. return 0.0;
  751. if( context->GetConstraint() == DRC_CONSTRAINT_T::LENGTH_CONSTRAINT
  752. || context->GetConstraint() == DRC_CONSTRAINT_T::SKEW_CONSTRAINT )
  753. {
  754. // DRC engine evaluates these singly, so we won't have a B item
  755. return 1.0;
  756. }
  757. return b && b->GetNetname() == coupledNet;
  758. } );
  759. }
  760. #define MISSING_DP_ARG( f ) \
  761. wxString::Format( _( "Missing diff-pair name argument to %s." ), f )
  762. static void inDiffPairFunc( LIBEVAL::CONTEXT* aCtx, void* self )
  763. {
  764. LIBEVAL::VALUE* argv = aCtx->Pop();
  765. PCBEXPR_VAR_REF* vref = static_cast<PCBEXPR_VAR_REF*>( self );
  766. BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr;
  767. LIBEVAL::VALUE* result = aCtx->AllocValue();
  768. result->Set( 0.0 );
  769. aCtx->Push( result );
  770. if( !argv || argv->AsString().IsEmpty() )
  771. {
  772. if( aCtx->HasErrorCallback() )
  773. aCtx->ReportError( MISSING_DP_ARG( wxT( "inDiffPair()" ) ) );
  774. return;
  775. }
  776. if( !item || !item->GetBoard() )
  777. return;
  778. result->SetDeferredEval(
  779. [item, argv]() -> double
  780. {
  781. if( item && item->IsConnected() )
  782. {
  783. NETINFO_ITEM* netinfo = static_cast<BOARD_CONNECTED_ITEM*>( item )->GetNet();
  784. if( !netinfo )
  785. return 0.0;
  786. wxString refName = netinfo->GetNetname();
  787. wxString arg = argv->AsString();
  788. wxString baseName, coupledNet;
  789. int polarity = DRC_ENGINE::MatchDpSuffix( refName, coupledNet, baseName );
  790. if( polarity != 0 && item->GetBoard()->FindNet( coupledNet ) )
  791. {
  792. if( baseName.Matches( arg ) )
  793. return 1.0;
  794. if( baseName.EndsWith( "_" ) && baseName.BeforeLast( '_' ).Matches( arg ) )
  795. return 1.0;
  796. }
  797. }
  798. return 0.0;
  799. } );
  800. }
  801. static void getFieldFunc( LIBEVAL::CONTEXT* aCtx, void* self )
  802. {
  803. LIBEVAL::VALUE* arg = aCtx->Pop();
  804. PCBEXPR_VAR_REF* vref = static_cast<PCBEXPR_VAR_REF*>( self );
  805. BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr;
  806. LIBEVAL::VALUE* result = aCtx->AllocValue();
  807. result->Set( "" );
  808. aCtx->Push( result );
  809. if( !arg )
  810. {
  811. if( aCtx->HasErrorCallback() )
  812. {
  813. aCtx->ReportError( wxString::Format( _( "Missing field name argument to %s." ),
  814. wxT( "getField()" ) ) );
  815. }
  816. return;
  817. }
  818. if( !item || !item->GetBoard() )
  819. return;
  820. result->SetDeferredEval(
  821. [item, arg]() -> wxString
  822. {
  823. if( item && item->Type() == PCB_FOOTPRINT_T )
  824. {
  825. FOOTPRINT* fp = static_cast<FOOTPRINT*>( item );
  826. PCB_FIELD* field = fp->GetFieldByName( arg->AsString() );
  827. if( field )
  828. return field->GetText();
  829. }
  830. return "";
  831. } );
  832. }
  833. PCBEXPR_BUILTIN_FUNCTIONS::PCBEXPR_BUILTIN_FUNCTIONS()
  834. {
  835. RegisterAllFunctions();
  836. }
  837. void PCBEXPR_BUILTIN_FUNCTIONS::RegisterAllFunctions()
  838. {
  839. m_funcs.clear();
  840. RegisterFunc( wxT( "existsOnLayer('x')" ), existsOnLayerFunc );
  841. RegisterFunc( wxT( "isPlated()" ), isPlatedFunc );
  842. RegisterFunc( wxT( "insideCourtyard('x') DEPRECATED" ), intersectsCourtyardFunc );
  843. RegisterFunc( wxT( "insideFrontCourtyard('x') DEPRECATED" ), intersectsFrontCourtyardFunc );
  844. RegisterFunc( wxT( "insideBackCourtyard('x') DEPRECATED" ), intersectsBackCourtyardFunc );
  845. RegisterFunc( wxT( "intersectsCourtyard('x')" ), intersectsCourtyardFunc );
  846. RegisterFunc( wxT( "intersectsFrontCourtyard('x')" ), intersectsFrontCourtyardFunc );
  847. RegisterFunc( wxT( "intersectsBackCourtyard('x')" ), intersectsBackCourtyardFunc );
  848. RegisterFunc( wxT( "insideArea('x') DEPRECATED" ), intersectsAreaFunc );
  849. RegisterFunc( wxT( "intersectsArea('x')" ), intersectsAreaFunc );
  850. RegisterFunc( wxT( "enclosedByArea('x')" ), enclosedByAreaFunc );
  851. RegisterFunc( wxT( "isMicroVia()" ), isMicroVia );
  852. RegisterFunc( wxT( "isBlindBuriedVia()" ), isBlindBuriedViaFunc );
  853. RegisterFunc( wxT( "memberOf('x') DEPRECATED" ), memberOfGroupFunc );
  854. RegisterFunc( wxT( "memberOfGroup('x')" ), memberOfGroupFunc );
  855. RegisterFunc( wxT( "memberOfFootprint('x')" ), memberOfFootprintFunc );
  856. RegisterFunc( wxT( "memberOfSheet('x')" ), memberOfSheetFunc );
  857. RegisterFunc( wxT( "fromTo('x','y')" ), fromToFunc );
  858. RegisterFunc( wxT( "isCoupledDiffPair()" ), isCoupledDiffPairFunc );
  859. RegisterFunc( wxT( "inDiffPair('x')" ), inDiffPairFunc );
  860. RegisterFunc( wxT( "getField('x')" ), getFieldFunc );
  861. }