KiCad PCB EDA Suite
microwave_inductor.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) 2017 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 #include "microwave_inductor.h"
25 
26 #include <wx/wx.h>
27 
28 #include <base_units.h>
29 #include <dialog_text_entry.h>
31 #include <pcb_edit_frame.h>
32 #include <validators.h>
33 
34 #include <class_pad.h>
35 #include <class_edge_mod.h>
36 #include <class_module.h>
37 #include <math/util.h> // for KiROUND
38 
39 
40 using namespace MWAVE;
41 
52 static void gen_arc( std::vector <wxPoint>& aBuffer,
53  const wxPoint& aStartPoint,
54  const wxPoint& aCenter,
55  int a_ArcAngle )
56 {
57  auto first_point = aStartPoint - aCenter;
58  auto radius = KiROUND( EuclideanNorm( first_point ) );
59  int seg_count = std::max( GetArcToSegmentCount( radius, ARC_HIGH_DEF, a_ArcAngle / 10.0 ), 3 );
60 
61  double increment_angle = (double) a_ArcAngle * M_PI / 1800 / seg_count;
62 
63  // Creates nb_seg point to approximate arc by segments:
64  for( int ii = 1; ii <= seg_count; ii++ )
65  {
66  double rot_angle = increment_angle * ii;
67  double fcos = cos( rot_angle );
68  double fsin = sin( rot_angle );
69  wxPoint currpt;
70 
71  // Rotate current point:
72  currpt.x = KiROUND( ( first_point.x * fcos + first_point.y * fsin ) );
73  currpt.y = KiROUND( ( first_point.y * fcos - first_point.x * fsin ) );
74 
75  auto corner = aCenter + currpt;
76  aBuffer.push_back( corner );
77  }
78 }
79 
80 
82 {
83  OK,
84  TOO_LONG,
85  TOO_SHORT,
86  NO_REPR,
87 };
88 
89 
99 static INDUCTOR_S_SHAPE_RESULT BuildCornersList_S_Shape( std::vector<wxPoint>& aBuffer,
100  const wxPoint& aStartPoint, const wxPoint& aEndPoint, int aLength, int aWidth )
101 {
102 /* We must determine:
103  * segm_count = number of segments perpendicular to the direction
104  * segm_len = length of a strand
105  * radius = radius of rounded parts of the coil
106  * stubs_len = length of the 2 stubs( segments parallel to the direction)
107  * connecting the start point to the start point of the S shape
108  * and the ending point to the end point of the S shape
109  * The equations are (assuming the area size of the entire shape is Size:
110  * Size.x = 2 * radius + segm_len
111  * Size.y = (segm_count + 2 ) * 2 * radius + 2 * stubs_len
112  * inductorPattern.m_length = 2 * delta // connections to the coil
113  * + (segm_count-2) * segm_len // length of the strands except 1st and last
114  * + (segm_count) * (PI * radius) // length of rounded
115  * segm_len + / 2 - radius * 2) // length of 1st and last bit
116  *
117  * The constraints are:
118  * segm_count >= 2
119  * radius < m_Size.x
120  * Size.y = (radius * 4) + (2 * stubs_len)
121  * segm_len > radius * 2
122  *
123  * The calculation is conducted in the following way:
124  * first:
125  * segm_count = 2
126  * radius = 4 * Size.x (arbitrarily fixed value)
127  * Then:
128  * Increasing the number of segments to the desired length
129  * (radius decreases if necessary)
130  */
131  wxPoint size;
132 
133  // This scale factor adjusts the arc length to handle
134  // the arc to segment approximation.
135  // because we use SEGM_COUNT_PER_360DEG segment to approximate a circle,
136  // the trace len must be corrected when calculated using arcs
137  // this factor adjust calculations and must be changed if SEGM_COUNT_PER_360DEG is modified
138  // because trace using segment is shorter the corresponding arc
139  // ADJUST_SIZE is the ratio between tline len and the arc len for an arc
140  // of 360/ADJUST_SIZE angle
141  #define ADJUST_SIZE 0.988
142 
143  auto pt = aEndPoint - aStartPoint;
144  double angle = -ArcTangente( pt.y, pt.x );
145  int min_len = KiROUND( EuclideanNorm( pt ) );
146  int segm_len = 0; // length of segments
147  int full_len; // full len of shape (sum of length of all segments + arcs)
148 
149 
150  /* Note: calculations are made for a vertical coil (more easy calculations)
151  * and after points are rotated to their actual position
152  * So the main direction is the Y axis.
153  * the 2 stubs are on the Y axis
154  * the others segments are parallel to the X axis.
155  */
156 
157  // Calculate the size of area (for a vertical shape)
158  size.x = min_len / 2;
159  size.y = min_len;
160 
161  // Choose a reasonable starting value for the radius of the arcs.
162  int radius = std::min( aWidth * 5, size.x / 4 );
163 
164  int segm_count; // number of full len segments
165  // the half size segments (first and last segment) are not counted here
166  int stubs_len = 0; // length of first or last segment (half size of others segments)
167 
168  for( segm_count = 0; ; segm_count++ )
169  {
170  stubs_len = ( size.y - ( radius * 2 * (segm_count + 2 ) ) ) / 2;
171 
172  if( stubs_len < size.y / 10 ) // Reduce radius.
173  {
174  stubs_len = size.y / 10;
175  radius = ( size.y - (2 * stubs_len) ) / ( 2 * (segm_count + 2) );
176 
177  if( radius < aWidth ) // Radius too small.
178  {
179  // Unable to create line: Requested length value is too large for room
181  }
182  }
183 
184  segm_len = size.x - ( radius * 2 );
185  full_len = 2 * stubs_len; // Length of coil connections.
186  full_len += segm_len * segm_count; // Length of full length segments.
187  full_len += KiROUND( ( segm_count + 2 ) * M_PI * ADJUST_SIZE * radius ); // Ard arcs len
188  full_len += segm_len - (2 * radius); // Length of first and last segments
189  // (half size segments len = segm_len/2 - radius).
190 
191  if( full_len >= aLength )
192  break;
193  }
194 
195  // Adjust len by adjusting segm_len:
196  int delta_size = full_len - aLength;
197 
198  // reduce len of the segm_count segments + 2 half size segments (= 1 full size segment)
199  segm_len -= delta_size / (segm_count + 1);
200 
201  // at this point, it could still be that the requested length is too
202  // short (because 4 quarter-circles are too long)
203  // to fix this is a relatively complex numerical problem which probably
204  // needs a refactor in this area. For now, just reject these cases:
205  {
206  const int min_total_length = 2 * stubs_len + 2 * M_PI * ADJUST_SIZE * radius;
207  if( min_total_length > aLength )
208  {
209  // we can't express this inductor with 90-deg arcs of this radius
211  }
212  }
213 
214  if( segm_len - 2 * radius < 0 )
215  {
216  // we can't represent this exact requested length with this number
217  // of segments (using the current algorithm). This stems from when
218  // you add a segment, you also add another half-circle, so there's a
219  // little bit of "dead" space.
220  // It's a bit ugly to just reject the input, as it might be possible
221  // to tweak the radius, but, again, that probably needs a refactor.
223  }
224 
225  // Generate first line (the first stub) and first arc (90 deg arc)
226  pt = aStartPoint;
227  aBuffer.push_back( pt );
228  pt.y += stubs_len;
229  aBuffer.push_back( pt );
230 
231  auto centre = pt;
232  centre.x -= radius;
233  gen_arc( aBuffer, pt, centre, -900 );
234  pt = aBuffer.back();
235 
236  int half_size_seg_len = segm_len / 2 - radius;
237 
238  if( half_size_seg_len )
239  {
240  pt.x -= half_size_seg_len;
241  aBuffer.push_back( pt );
242  }
243 
244  // Create shape.
245  int ii;
246  int sign = 1;
247  segm_count += 1; // increase segm_count to create the last half_size segment
248 
249  for( ii = 0; ii < segm_count; ii++ )
250  {
251  int arc_angle;
252 
253  if( ii & 1 ) // odd order arcs are greater than 0
254  sign = -1;
255  else
256  sign = 1;
257 
258  arc_angle = 1800 * sign;
259  centre = pt;
260  centre.y += radius;
261  gen_arc( aBuffer, pt, centre, arc_angle );
262  pt = aBuffer.back();
263  pt.x += segm_len * sign;
264  aBuffer.push_back( pt );
265  }
266 
267  // The last point is false:
268  // it is the end of a full size segment, but must be
269  // the end of the second half_size segment. Change it.
270  sign *= -1;
271  aBuffer.back().x = aStartPoint.x + radius * sign;
272 
273  // create last arc
274  pt = aBuffer.back();
275  centre = pt;
276  centre.y += radius;
277  gen_arc( aBuffer, pt, centre, 900 * sign );
278 
279  // Rotate point
280  angle += 900;
281 
282  for( unsigned jj = 0; jj < aBuffer.size(); jj++ )
283  {
284  RotatePoint( &aBuffer[jj], aStartPoint, angle );
285  }
286 
287  // push last point (end point)
288  aBuffer.push_back( aEndPoint );
289 
291 }
292 
293 
295  PCB_EDIT_FRAME* aPcbFrame, wxString& aErrorMessage )
296 {
297  /* Build a microwave inductor footprint.
298  * - Length Mself.lng
299  * - Extremities Mself.m_Start and Mself.m_End
300  * We must determine:
301  * Mself.nbrin = number of segments perpendicular to the direction
302  * (The coil nbrin will demicercles + 1 + 2 1 / 4 circle)
303  * Mself.lbrin = length of a strand
304  * Mself.radius = radius of rounded parts of the coil
305  * Mself.delta = segments extremities connection between him and the coil even
306  *
307  * The equations are
308  * Mself.m_Size.x = 2 * Mself.radius + Mself.lbrin
309  * Mself.m_Size.y * Mself.delta = 2 + 2 * Mself.nbrin * Mself.radius
310  * Mself.lng = 2 * Mself.delta / / connections to the coil
311  + (Mself.nbrin-2) * Mself.lbrin / / length of the strands except 1st and last
312  + (Mself.nbrin 1) * (PI * Mself.radius) / / length of rounded
313  * Mself.lbrin + / 2 - Melf.radius * 2) / / length of 1st and last bit
314  *
315  * The constraints are:
316  * Nbrin >= 2
317  * Mself.radius < Mself.m_Size.x
318  * Mself.m_Size.y = Mself.radius * 4 + 2 * Mself.raccord
319  * Mself.lbrin> Mself.radius * 2
320  *
321  * The calculation is conducted in the following way:
322  * Initially:
323  * Nbrin = 2
324  * Radius = 4 * m_Size.x (arbitrarily fixed value)
325  * Then:
326  * Increasing the number of segments to the desired length
327  * (Radius decreases if necessary)
328  */
329 
330  D_PAD* pad;
331  wxString msg;
332 
333  auto pt = inductorPattern.m_End - inductorPattern.m_Start;
334  int min_len = KiROUND( EuclideanNorm( pt ) );
335  inductorPattern.m_length = min_len;
336 
337  // Enter the desired length.
338  msg = StringFromValue( aPcbFrame->GetUserUnits(), inductorPattern.m_length, true );
339  WX_TEXT_ENTRY_DIALOG dlg( aPcbFrame, _( "Length of Trace:" ), wxEmptyString, msg );
340 
341  if( dlg.ShowModal() != wxID_OK )
342  return nullptr; // canceled by user
343 
344  msg = dlg.GetValue();
345  inductorPattern.m_length = ValueFromString( aPcbFrame->GetUserUnits(), msg );
346 
347  // Control values (ii = minimum length)
348  if( inductorPattern.m_length < min_len )
349  {
350  aErrorMessage = _( "Requested length < minimum length" );
351  return nullptr;
352  }
353 
354  // Calculate the elements.
355  std::vector <wxPoint> buffer;
356  const INDUCTOR_S_SHAPE_RESULT res = BuildCornersList_S_Shape( buffer, inductorPattern.m_Start,
357  inductorPattern.m_End, inductorPattern.m_length, inductorPattern.m_Width );
358 
359  switch( res )
360  {
362  aErrorMessage = _( "Requested length too large" );
363  return nullptr;
365  aErrorMessage = _( "Requested length too small" );
366  return nullptr;
368  aErrorMessage = _( "Requested length can't be represented" );
369  return nullptr;
371  break;
372  }
373 
374  // Generate footprint. the value is also used as footprint name.
375  msg = "L";
376  WX_TEXT_ENTRY_DIALOG cmpdlg( aPcbFrame, _( "Component Value:" ), wxEmptyString, msg );
378 
379  if( ( cmpdlg.ShowModal() != wxID_OK ) || msg.IsEmpty() )
380  return nullptr; // Aborted by user
381 
382  MODULE* module = aPcbFrame->CreateNewModule( msg );
383  aPcbFrame->AddModuleToBoard( module );
384 
385  module->SetFPID( LIB_ID( wxEmptyString, wxT( "mw_inductor" ) ) );
386  module->SetAttributes( MOD_VIRTUAL | MOD_CMS );
387  module->ClearFlags();
388  module->SetPosition( inductorPattern.m_End );
389 
390  // Generate segments
391  for( unsigned jj = 1; jj < buffer.size(); jj++ )
392  {
393  EDGE_MODULE* PtSegm;
394  PtSegm = new EDGE_MODULE( module );
395  PtSegm->SetStart( buffer[jj - 1] );
396  PtSegm->SetEnd( buffer[jj] );
397  PtSegm->SetWidth( inductorPattern.m_Width );
398  PtSegm->SetLayer( module->GetLayer() );
399  PtSegm->SetShape( S_SEGMENT );
400  PtSegm->SetStart0( PtSegm->GetStart() - module->GetPosition() );
401  PtSegm->SetEnd0( PtSegm->GetEnd() - module->GetPosition() );
402  module->Add( PtSegm );
403  }
404 
405  // Place a pad on each end of coil.
406  pad = new D_PAD( module );
407 
408  module->Add( pad );
409 
410  pad->SetName( "1" );
411  pad->SetPosition( inductorPattern.m_End );
412  pad->SetPos0( pad->GetPosition() - module->GetPosition() );
413 
414  pad->SetSize( wxSize( inductorPattern.m_Width, inductorPattern.m_Width ) );
415 
416  pad->SetLayerSet( LSET( module->GetLayer() ) );
418  pad->SetShape( PAD_SHAPE_CIRCLE );
419 
420  D_PAD* newpad = new D_PAD( *pad );
421 
422  module->Add( newpad );
423 
424  pad = newpad;
425  pad->SetName( "2" );
426  pad->SetPosition( inductorPattern.m_Start );
427  pad->SetPos0( pad->GetPosition() - module->GetPosition() );
428 
429  // Modify text positions.
430  wxPoint refPos( ( inductorPattern.m_Start.x + inductorPattern.m_End.x ) / 2,
431  ( inductorPattern.m_Start.y + inductorPattern.m_End.y ) / 2 );
432 
433  wxPoint valPos = refPos;
434 
435  refPos.y -= module->Reference().GetTextSize().y;
436  module->Reference().SetPosition( refPos );
437  valPos.y += module->Value().GetTextSize().y;
438  module->Value().SetPosition( valPos );
439 
440  module->CalculateBoundingBox();
441  return module;
442 }
double EuclideanNorm(const wxPoint &vector)
Euclidean norm of a 2D vector.
Definition: trigo.h:123
void SetEnd0(const wxPoint &aPoint)
int sign(T val)
Definition: util.h:90
TEXTE_MODULE & Reference()
Definition: class_module.h:477
void SetShape(STROKE_T aShape)
virtual void AddModuleToBoard(MODULE *module)
Adds the given module to the board.
virtual void SetLayer(PCB_LAYER_ID aLayer)
Function SetLayer sets the layer this item is on.
Implementation of conversion functions that require both schematic and board internal units.
void CalculateBoundingBox()
Function CalculateBoundingBox calculates the bounding box in board coordinates.
Parameters for construction of a microwave inductor.
void SetTextValidator(wxTextValidatorStyle style)
Smd pad, appears on the solder paste layer (default)
Definition: pad_shapes.h:62
Set for modules listed in the automatic insertion list (usually SMD footprints)
Definition: class_module.h:74
wxString StringFromValue(EDA_UNITS aUnits, double aValue, bool aAddUnitSymbol, bool aUseMils)
Function StringFromValue returns the string from aValue according to units (inch, mm ....
Definition: base_units.cpp:219
void SetPosition(const wxPoint &aPos) override
Definition: class_pad.h:240
usual segment : line with rounded ends
void RotatePoint(int *pX, int *pY, double angle)
Definition: trigo.cpp:208
A logical library item identifier and consists of various portions much like a URI.
Definition: lib_id.h:51
MODULE * CreateNewModule(const wxString &aModuleName)
Function CreateNewModule Creates a new module or footprint, at position 0,0 The new module contains o...
const wxPoint & GetEnd() const
Function GetEnd returns the ending point of the graphic.
Definition: erc.h:42
static INDUCTOR_S_SHAPE_RESULT BuildCornersList_S_Shape(std::vector< wxPoint > &aBuffer, const wxPoint &aStartPoint, const wxPoint &aEndPoint, int aLength, int aWidth)
Function BuildCornersList_S_Shape Create a path like a S-shaped coil.
INDUCTOR_S_SHAPE_RESULT
LSET is a set of PCB_LAYER_IDs.
void SetName(const wxString &aName)
Set the pad name (sometimes called pad number, although it can be an array reference like AA12).
Definition: class_pad.h:187
TEXTE_MODULE & Value()
read/write accessors:
Definition: class_module.h:476
void SetPos0(const wxPoint &aPos)
Definition: class_pad.h:293
const wxSize & GetTextSize() const
Definition: eda_text.h:223
static void gen_arc(std::vector< wxPoint > &aBuffer, const wxPoint &aStartPoint, const wxPoint &aCenter, int a_ArcAngle)
Function gen_arc generates an arc using arc approximation by lines: Center aCenter Angle "angle" (in ...
This class provides a custom wxValidator object for limiting the allowable characters when defining f...
Definition: validators.h:63
void SetSize(const wxSize &aSize)
Definition: class_pad.h:299
a few functions useful in geometry calculations.
virtual void SetPosition(const wxPoint &aPos) override
void SetPosition(const wxPoint &aPos) override
void SetAttribute(PAD_ATTR_T aAttribute)
Definition: class_pad.cpp:423
Pad object description.
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT) override
void SetStart(const wxPoint &aStart)
void SetLayerSet(LSET aLayerMask)
Definition: class_pad.h:441
MODULE * CreateMicrowaveInductor(INDUCTOR_PATTERN &aPattern, PCB_EDIT_FRAME *aPcbFrame, wxString &aErrorMessage)
Creates an S-shaped coil footprint for microwave applications.
#define ADJUST_SIZE
#define _(s)
Definition: 3d_actions.cpp:31
Virtual component: when created by copper shapes on board (Like edge card connectors,...
Definition: class_module.h:76
static DIRECTION_45::AngleType angle(const VECTOR2I &a, const VECTOR2I &b)
void SetStart0(const wxPoint &aPoint)
PCB_EDIT_FRAME is the main frame for Pcbnew.
Requested length too short.
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:61
void SetShape(PAD_SHAPE_T aShape)
Definition: class_pad.h:238
void SetEnd(const wxPoint &aEnd)
void ClearFlags(STATUS_FLAGS aMask=EDA_ITEM_ALL_FLAGS)
Definition: base_struct.h:256
const wxPoint & GetStart() const
Function GetStart returns the starting point of the graphic.
long long int ValueFromString(EDA_UNITS aUnits, const wxString &aTextValue, bool aUseMils)
Function ValueFromString converts aTextValue in aUnits to internal units used by the application.
Definition: base_units.cpp:429
Module description (excepted pads)
double ArcTangente(int dy, int dx)
Definition: trigo.cpp:162
const wxPoint GetPosition() const override
Definition: class_pad.h:241
EDGE_MODULE class definition.
Requested length too long.
virtual PCB_LAYER_ID GetLayer() const
Function GetLayer returns the primary layer this item is on.
const wxPoint GetPosition() const override
Definition: class_module.h:210
int GetArcToSegmentCount(int aRadius, int aErrorMax, double aArcAngleDegree)
void SetFPID(const LIB_ID &aFPID)
Definition: class_module.h:220
Custom text control validator definitions.
EDA_UNITS GetUserUnits() const
Return the user units currently in use.
void SetAttributes(int aAttributes)
Definition: class_module.h:260
void SetWidth(int aWidth)