KiCad PCB EDA Suite
pcb_expr_evaluator.cpp
Go to the documentation of this file.
1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2019-2020 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 
24 
25 #include <cstdio>
26 #include <memory>
27 #include <reporter.h>
28 #include <class_board.h>
29 #include <class_track.h>
30 #include <pcb_expr_evaluator.h>
31 
32 
33 static void onLayer( LIBEVAL::CONTEXT* aCtx, void *self )
34 {
35  PCB_EXPR_VAR_REF* vref = static_cast<PCB_EXPR_VAR_REF*>( self );
36  BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr;
37 
38  LIBEVAL::VALUE* arg = aCtx->Pop();
39  LIBEVAL::VALUE* result = aCtx->AllocValue();
40 
41  result->Set( 0.0 );
42  aCtx->Push( result );
43 
44  if( !item )
45  return;
46 
47  if( !arg )
48  {
49  aCtx->ReportError( wxString::Format( _( "Missing argument to '%s'" ),
50  wxT( "onLayer()" ) ) );
51  return;
52  }
53 
54  wxString layerName = arg->AsString();
55  wxPGChoices& layerMap = ENUM_MAP<PCB_LAYER_ID>::Instance().Choices();
56  bool anyMatch = false;
57 
58  for( unsigned ii = 0; ii < layerMap.GetCount(); ++ii )
59  {
60  wxPGChoiceEntry& entry = layerMap[ii];
61 
62  if( entry.GetText().Matches( layerName ) )
63  {
64  anyMatch = true;
65 
66  if( item->IsOnLayer( ToLAYER_ID( entry.GetValue() ) ) )
67  {
68  result->Set( 1.0 );
69  return;
70  }
71  }
72  }
73 
74  if( !anyMatch )
75  aCtx->ReportError( wxString::Format( _( "Unrecognized layer '%s'" ), layerName ) );
76 }
77 
78 
79 static void isPlated( LIBEVAL::CONTEXT* aCtx, void* self )
80 {
81  LIBEVAL::VALUE* result = aCtx->AllocValue();
82 
83  result->Set( 0.0 );
84  aCtx->Push( result );
85 
86  PCB_EXPR_VAR_REF* vref = static_cast<PCB_EXPR_VAR_REF*>( self );
87  BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr;
88  D_PAD* pad = dynamic_cast<D_PAD*>( item );
89 
90  if( pad && pad->GetAttribute() == PAD_ATTRIB_STANDARD )
91  result->Set( 1.0 );
92 }
93 
94 
95 static void insideCourtyard( LIBEVAL::CONTEXT* aCtx, void* self )
96 {
97  PCB_EXPR_CONTEXT* context = static_cast<PCB_EXPR_CONTEXT*>( aCtx );
98  LIBEVAL::VALUE* arg = aCtx->Pop();
99  LIBEVAL::VALUE* result = aCtx->AllocValue();
100 
101  result->Set( 0.0 );
102  aCtx->Push( result );
103 
104  if( !arg )
105  {
106  aCtx->ReportError( wxString::Format( _( "Missing argument to '%s'" ),
107  wxT( "insideCourtyard()" ) ) );
108  return;
109  }
110 
111  PCB_EXPR_VAR_REF* vref = static_cast<PCB_EXPR_VAR_REF*>( self );
112  BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr;
113  MODULE* footprint = nullptr;
114 
115  if( !item )
116  return;
117 
118  if( arg->AsString() == "A" )
119  {
120  footprint = dynamic_cast<MODULE*>( context->GetItem( 0 ) );
121  }
122  else if( arg->AsString() == "B" )
123  {
124  footprint = dynamic_cast<MODULE*>( context->GetItem( 1 ) );
125  }
126  else
127  {
128  for( MODULE* candidate : item->GetBoard()->Modules() )
129  {
130  if( candidate->GetReference().Matches( arg->AsString() ) )
131  {
132  footprint = candidate;
133  break;
134  }
135  }
136  }
137 
138  if( footprint )
139  {
140  SHAPE_POLY_SET footprintCourtyard;
141 
142  if( footprint->IsFlipped() )
143  footprintCourtyard = footprint->GetPolyCourtyardBack();
144  else
145  footprintCourtyard = footprint->GetPolyCourtyardFront();
146 
147  SHAPE_POLY_SET testPoly;
148 
149  item->TransformShapeWithClearanceToPolygon( testPoly, context->GetLayer(), 0 );
150  testPoly.BooleanIntersection( footprintCourtyard, SHAPE_POLY_SET::PM_FAST );
151 
152  if( testPoly.OutlineCount() )
153  result->Set( 1.0 );
154  }
155 }
156 
157 
158 static void insideArea( LIBEVAL::CONTEXT* aCtx, void* self )
159 {
160  PCB_EXPR_CONTEXT* context = static_cast<PCB_EXPR_CONTEXT*>( aCtx );
161  LIBEVAL::VALUE* arg = aCtx->Pop();
162  LIBEVAL::VALUE* result = aCtx->AllocValue();
163 
164  result->Set( 0.0 );
165  aCtx->Push( result );
166 
167  if( !arg )
168  {
169  aCtx->ReportError( wxString::Format( _( "Missing argument to '%s'" ),
170  wxT( "insideArea()" ) ) );
171  return;
172  }
173 
174  PCB_EXPR_VAR_REF* vref = static_cast<PCB_EXPR_VAR_REF*>( self );
175  BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr;
176  ZONE_CONTAINER* zone = nullptr;
177 
178  if( !item )
179  return;
180 
181  if( arg->AsString() == "A" )
182  {
183  zone = dynamic_cast<ZONE_CONTAINER*>( context->GetItem( 0 ) );
184  }
185  else if( arg->AsString() == "B" )
186  {
187  zone = dynamic_cast<ZONE_CONTAINER*>( context->GetItem( 1 ) );
188  }
189  else
190  {
191  for( ZONE_CONTAINER* candidate : item->GetBoard()->Zones() )
192  {
193  if( candidate->GetZoneName().Matches( arg->AsString() ) )
194  {
195  zone = candidate;
196  break;
197  }
198  }
199  }
200 
201  if( zone )
202  {
203  SHAPE_POLY_SET testPoly;
204 
205  item->TransformShapeWithClearanceToPolygon( testPoly, context->GetLayer(), 0 );
207 
208  if( testPoly.OutlineCount() )
209  result->Set( 1.0 );
210  }
211 }
212 
213 
214 static void isMicroVia( LIBEVAL::CONTEXT* aCtx, void* self )
215 {
216  PCB_EXPR_VAR_REF* vref = static_cast<PCB_EXPR_VAR_REF*>( self );
217  BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr;
218  LIBEVAL::VALUE* result = aCtx->AllocValue();
219 
220  result->Set( 0.0 );
221  aCtx->Push( result );
222 
223  auto via = dyn_cast<VIA*>( item );
224 
225  if( via && via->GetViaType() == VIATYPE::MICROVIA )
226  {
227  result->Set ( 1.0 );
228  }
229 }
230 
231 
232 static void isBlindBuriedVia( LIBEVAL::CONTEXT* aCtx, void* self )
233 {
234  PCB_EXPR_VAR_REF* vref = static_cast<PCB_EXPR_VAR_REF*>( self );
235  BOARD_ITEM* item = vref ? vref->GetObject( aCtx ) : nullptr;
236  LIBEVAL::VALUE* result = aCtx->AllocValue();
237 
238  result->Set( 0.0 );
239  aCtx->Push( result );
240 
241  auto via = dyn_cast<VIA*>( item );
242 
243  if( via && via->GetViaType() == VIATYPE::BLIND_BURIED )
244  {
245  result->Set ( 1.0 );
246  }
247 
248 }
249 
250 
252 {
253  auto registerFunc = [&]( const wxString& funcSignature, LIBEVAL::FUNC_CALL_REF funcPtr )
254  {
255  wxString funcName = funcSignature.BeforeFirst( '(' );
256  m_funcs[ std::string( funcName.Lower() ) ] = std::move( funcPtr );
257  m_funcSigs.Add( funcSignature );
258  };
259 
260  registerFunc( "onLayer('x')", onLayer );
261  registerFunc( "isPlated()", isPlated );
262  registerFunc( "insideCourtyard('x')", insideCourtyard );
263  registerFunc( "insideArea('x')", insideArea );
264  registerFunc( "isMicroVia()", isMicroVia );
265  registerFunc( "isBlindBuriedVia()", isBlindBuriedVia );
266 }
267 
268 
270 {
271  wxASSERT( dynamic_cast<PCB_EXPR_CONTEXT*>( aCtx ) );
272 
273  const PCB_EXPR_CONTEXT* ctx = static_cast<const PCB_EXPR_CONTEXT*>( aCtx );
274  BOARD_ITEM* item = ctx->GetItem( m_itemIndex );
275  return item;
276 }
277 
278 
280 {
281  BOARD_ITEM* item = const_cast<BOARD_ITEM*>( GetObject( aCtx ) );
282  auto it = m_matchingTypes.find( TYPE_HASH( *item ) );
283 
284  if( it == m_matchingTypes.end() )
285  {
286  // Don't force user to type "A.Type == 'via' && A.Via_Type == 'buried'" when the
287  // simplier "A.Via_Type == 'buried'" is perfectly clear. Instead, return an undefined
288  // value when the property doesn't appear on a particular object.
289 
290  return LIBEVAL::VALUE( "UNDEFINED" );
291  }
292  else
293  {
294  if( m_type == LIBEVAL::VT_NUMERIC )
295  return LIBEVAL::VALUE( (double) item->Get<int>( it->second ) );
296  else
297  {
298  wxString str;
299 
300  if( !m_isEnum )
301  {
302  str = item->Get<wxString>( it->second );
303  }
304  else
305  {
306  const wxAny& any = item->Get( it->second );
307  any.GetAs<wxString>( &str );
308  }
309 
310  return LIBEVAL::VALUE( str );
311  }
312  }
313 }
314 
315 
317 {
319 
320  return registry.Get( aName.Lower() );
321 }
322 
323 
324 std::unique_ptr<LIBEVAL::VAR_REF> PCB_EXPR_UCODE::CreateVarRef( const wxString& aVar, const wxString& aField )
325 {
327  std::unique_ptr<PCB_EXPR_VAR_REF> vref;;
328 
329  if( aVar == "A" )
330  {
331  vref.reset( new PCB_EXPR_VAR_REF( 0 ) );
332  }
333  else if( aVar == "B" )
334  {
335  vref.reset( new PCB_EXPR_VAR_REF( 1 ) );
336  }
337  else
338  {
339  return nullptr;
340  }
341 
342  if( aField.length() == 0 ) // return reference to base object
343  {
344  return std::move( vref );
345  }
346 
347  wxString field( aField );
348  field.Replace( "_", " " );
349 
350  for( const PROPERTY_MANAGER::CLASS_INFO& cls : propMgr.GetAllClasses() )
351  {
352  if( propMgr.IsOfType( cls.type, TYPE_HASH( BOARD_ITEM ) ) )
353  {
354  PROPERTY_BASE* prop = propMgr.GetProperty( cls.type, field );
355 
356  if( prop )
357  {
358  vref->AddAllowedClass( cls.type, prop );
359 
360  if( prop->TypeHash() == TYPE_HASH( int ) )
361  {
362  vref->SetType( LIBEVAL::VT_NUMERIC );
363  }
364  else if( prop->TypeHash() == TYPE_HASH( wxString ) )
365  {
366  vref->SetType( LIBEVAL::VT_STRING );
367  }
368  else if ( prop->HasChoices() )
369  { // it's an enum, we treat it as string
370  vref->SetType( LIBEVAL::VT_STRING );
371  vref->SetIsEnum ( true );
372  }
373  else
374  {
375  wxFAIL_MSG( "PCB_EXPR_UCODE::createVarRef: Unknown property type." );
376  }
377  }
378  }
379  }
380 
381  if( vref->GetType() == LIBEVAL::VT_UNDEFINED )
382  vref->SetType( LIBEVAL::VT_PARSE_ERROR );
383 
384  return std::move( vref );
385 }
386 
387 
389 {
390 public:
392  {
393  }
394 
395  virtual const std::vector<wxString>& GetSupportedUnits() const override
396  {
397  static const std::vector<wxString> pcbUnits = { "mil", "mm", "in" };
398 
399  return pcbUnits;
400  }
401 
402  virtual double Convert( const wxString& aString, int unitId ) const override
403  {
404  double v = wxAtof( aString );
405 
406  switch( unitId )
407  {
408  case 0: return DoubleValueFromString( EDA_UNITS::INCHES, aString, true );
409  case 1: return DoubleValueFromString( EDA_UNITS::MILLIMETRES, aString );
410  case 2: return DoubleValueFromString( EDA_UNITS::INCHES, aString, false );
411  default: return v;
412  }
413  };
414 };
415 
416 
418 {
419  m_unitResolver = std::make_unique<PCB_UNIT_RESOLVER>();
420 }
421 
422 
424 {
425  m_result = 0;
426 }
427 
429 {
430 }
431 
432 
433 bool PCB_EXPR_EVALUATOR::Evaluate( const wxString& aExpr )
434 {
435  PCB_EXPR_UCODE ucode;
436  PCB_EXPR_CONTEXT preflightContext( F_Cu );
437 
438  m_compiler.Compile( aExpr.ToUTF8().data(), &ucode, &preflightContext );
439 
440  PCB_EXPR_CONTEXT evaluationContext( F_Cu );
441  LIBEVAL::VALUE* result = ucode.Run( &evaluationContext );
442 
443  if( result->GetType() == LIBEVAL::VT_NUMERIC )
444  m_result = KiROUND( result->AsDouble() );
445 
446  return true;
447 }
448 
SHAPE_POLY_SET & GetPolyCourtyardFront()
Used in DRC to test the courtyard area (a complex polygon)
Definition: class_module.h:683
BOARD_ITEM * GetItem(int index) const
ZONE_CONTAINER handles a list of polygons defining a copper zone.
Definition: class_zone.h:61
virtual bool HasChoices() const
Returns true if this PROPERTY has a limited set of possible values.
Definition: property.h:213
static PROPERTY_MANAGER & Instance()
Definition: property_mgr.h:61
#define TYPE_HASH(x)
Macro to generate unique identifier for a type
Definition: property.h:53
int OutlineCount() const
Returns the number of outlines in the set
std::unique_ptr< UNIT_RESOLVER > m_unitResolver
double AsDouble() const
BOARD_ITEM * GetObject(LIBEVAL::CONTEXT *aCtx) const
bool IsFlipped() const
function IsFlipped
Definition: class_module.h:302
BOARD_ITEM is a base class for any item which can be embedded within the BOARD container class,...
virtual LIBEVAL::FUNC_CALL_REF CreateFuncCall(const wxString &aName) override
LIBEVAL::FUNC_CALL_REF Get(const wxString &name)
virtual size_t TypeHash() const =0
Returns type-id of the property type.
SHAPE_POLY_SET * Outline()
Definition: class_zone.h:300
static ENUM_MAP< T > & Instance()
Definition: property.h:517
double DoubleValueFromString(EDA_UNITS aUnits, const wxString &aTextValue, bool aUseMils, EDA_DATA_TYPE aType)
Function DoubleValueFromString converts aTextValue to a double.
Definition: base_units.cpp:346
static void isPlated(LIBEVAL::CONTEXT *aCtx, void *self)
static void onLayer(LIBEVAL::CONTEXT *aCtx, void *self)
bool Compile(const wxString &aString, UCODE *aCode, CONTEXT *aPreflightContext)
PAD_ATTR_T GetAttribute() const
Definition: class_pad.h:335
std::map< wxString, LIBEVAL::FUNC_CALL_REF > m_funcs
A single base class (TRACK) represents both tracks and vias, with subclasses for curved tracks (ARC) ...
PROPERTY_BASE * GetProperty(TYPE_ID aType, const wxString &aProperty) const
Returns a property for a specific type.
PCB_EXPR_COMPILER m_compiler
VAR_TYPE_T GetType() const
std::function< void(CONTEXT *, void *)> FUNC_CALL_REF
virtual bool IsOnLayer(PCB_LAYER_ID aLayer) const
Function IsOnLayer tests to see if this object is on the given layer.
static void isBlindBuriedVia(LIBEVAL::CONTEXT *aCtx, void *self)
MODULES & Modules()
Definition: class_board.h:247
LIBEVAL::VAR_TYPE_T m_type
SHAPE_POLY_SET.
wxAny Get(PROPERTY_BASE *aProperty)
Definition: inspectable.h:84
virtual std::unique_ptr< LIBEVAL::VAR_REF > CreateVarRef(const wxString &aVar, const wxString &aField) override
virtual BOARD * GetBoard() const
Function GetBoard returns the BOARD in which this BOARD_ITEM resides, or NULL if none.
const wxString & AsString() const
void BooleanIntersection(const SHAPE_POLY_SET &b, POLYGON_MODE aFastMode)
Performs boolean polyset intersection For aFastMode meaning, see function booleanOp
static void insideArea(LIBEVAL::CONTEXT *aCtx, void *self)
Field Value of part, i.e. "3.3K".
virtual LIBEVAL::VALUE GetValue(LIBEVAL::CONTEXT *aCtx) override
void ReportError(const wxString &aErrorMsg)
bool IsOfType(TYPE_ID aDerived, TYPE_ID aBase) const
Returns true if aDerived is inherited from aBase.
ZONE_CONTAINERS & Zones()
Definition: class_board.h:252
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, CPTREE &aTree)
Function Format outputs a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:201
#define _(s)
Definition: 3d_actions.cpp:33
static void insideCourtyard(LIBEVAL::CONTEXT *aCtx, void *self)
constexpr ret_type KiROUND(fp_type v)
Round a floating point number to an integer using "round halfway cases away from zero".
Definition: util.h:68
bool Evaluate(const wxString &aExpr)
static void isMicroVia(LIBEVAL::CONTEXT *aCtx, void *self)
virtual double Convert(const wxString &aString, int unitId) const override
virtual const std::vector< wxString > & GetSupportedUnits() const override
CLASSES_INFO GetAllClasses()
Provides class metadata.
Definition: property_mgr.h:58
VALUE * Run(CONTEXT *ctx)
SHAPE_POLY_SET & GetPolyCourtyardBack()
Definition: class_module.h:684
void Push(VALUE *v)
PCB_LAYER_ID ToLAYER_ID(int aLayer)
Definition: lset.cpp:886
virtual void TransformShapeWithClearanceToPolygon(SHAPE_POLY_SET &aCornerBuffer, PCB_LAYER_ID aLayer, int aClearanceValue, int aError=ARC_LOW_DEF, bool ignoreLineWidth=false) const
Function TransformShapeWithClearanceToPolygon Convert the item shape to a closed polygon Used in fill...
void Set(double aValue)
PCB_LAYER_ID GetLayer() const
std::unordered_map< TYPE_ID, PROPERTY_BASE * > m_matchingTypes
static PCB_EXPR_BUILTIN_FUNCTIONS & Instance()