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