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 <pad.h>
29 #include <fp_shape.h>
30 #include <footprint.h>
31 #include <confirm.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 = GetArcToSegmentCount( radius, ARC_HIGH_DEF, a_ArcAngle / 10.0 );
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 inductorFP = std::unique_ptr<FOOTPRINT>( createMicrowaveInductor( pattern, errorMessage ) );
307 
308  // on any error, report if we can
309  if ( !inductorFP || !errorMessage.IsEmpty() )
310  {
311  if ( !errorMessage.IsEmpty() )
312  editFrame.ShowInfoBarError( errorMessage );
313  }
314  else
315  {
316  // at this point, we can save the footprint
317  m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, inductorFP.get() );
318 
319  BOARD_COMMIT commit( this );
320  commit.Add( inductorFP.release() );
321  commit.Push( _("Add microwave inductor" ) );
322  }
323 }
324 
325 
327  wxString& aErrorMessage )
328 {
329  /* Build a microwave inductor footprint.
330  * - Length Mself.lng
331  * - Extremities Mself.m_Start and Mself.m_End
332  * We must determine:
333  * Mself.nbrin = number of segments perpendicular to the direction
334  * (The coil nbrin will demicercles + 1 + 2 1 / 4 circle)
335  * Mself.lbrin = length of a strand
336  * Mself.radius = radius of rounded parts of the coil
337  * Mself.delta = segments extremities connection between him and the coil even
338  *
339  * The equations are
340  * Mself.m_Size.x = 2 * Mself.radius + Mself.lbrin
341  * Mself.m_Size.y * Mself.delta = 2 + 2 * Mself.nbrin * Mself.radius
342  * Mself.lng = 2 * Mself.delta / / connections to the coil
343  + (Mself.nbrin-2) * Mself.lbrin / / length of the strands except 1st and last
344  + (Mself.nbrin 1) * (PI * Mself.radius) / / length of rounded
345  * Mself.lbrin + / 2 - Melf.radius * 2) / / length of 1st and last bit
346  *
347  * The constraints are:
348  * Nbrin >= 2
349  * Mself.radius < Mself.m_Size.x
350  * Mself.m_Size.y = Mself.radius * 4 + 2 * Mself.raccord
351  * Mself.lbrin> Mself.radius * 2
352  *
353  * The calculation is conducted in the following way:
354  * Initially:
355  * Nbrin = 2
356  * Radius = 4 * m_Size.x (arbitrarily fixed value)
357  * Then:
358  * Increasing the number of segments to the desired length
359  * (Radius decreases if necessary)
360  */
361 
362  PAD* pad;
363  wxString msg;
364 
365  PCB_EDIT_FRAME& editFrame = *getEditFrame<PCB_EDIT_FRAME>();
366 
367  auto pt = aInductorPattern.m_End - aInductorPattern.m_Start;
368  int min_len = KiROUND( EuclideanNorm( pt ) );
369  aInductorPattern.m_Length = min_len;
370 
371  // Enter the desired length.
372  msg = StringFromValue( editFrame.GetUserUnits(), aInductorPattern.m_Length );
373  WX_TEXT_ENTRY_DIALOG dlg( &editFrame, _( "Length of Trace:" ), wxEmptyString, msg );
374 
375  if( dlg.ShowQuasiModal() != wxID_OK )
376  return nullptr; // canceled by user
377 
378  msg = dlg.GetValue();
379  aInductorPattern.m_Length = ValueFromString( editFrame.GetUserUnits(), msg );
380 
381  // Control values (ii = minimum length)
382  if( aInductorPattern.m_Length < min_len )
383  {
384  aErrorMessage = _( "Requested length < minimum length" );
385  return nullptr;
386  }
387 
388  // Calculate the elements.
389  std::vector <wxPoint> buffer;
390  const INDUCTOR_S_SHAPE_RESULT res = BuildCornersList_S_Shape( buffer, aInductorPattern.m_Start,
391  aInductorPattern.m_End,
392  aInductorPattern.m_Length,
393  aInductorPattern.m_Width );
394 
395  switch( res )
396  {
398  aErrorMessage = _( "Requested length too large" );
399  return nullptr;
401  aErrorMessage = _( "Requested length too small" );
402  return nullptr;
404  aErrorMessage = _( "Requested length can't be represented" );
405  return nullptr;
407  break;
408  }
409 
410  // Generate footprint. the value is also used as footprint name.
411  msg = "L";
412  WX_TEXT_ENTRY_DIALOG cmpdlg( &editFrame, _( "Component Value:" ), wxEmptyString, msg );
413  cmpdlg.SetTextValidator( FOOTPRINT_NAME_VALIDATOR( &msg ) );
414 
415  if( ( cmpdlg.ShowQuasiModal() != wxID_OK ) || msg.IsEmpty() )
416  return nullptr; // Aborted by user
417 
418  FOOTPRINT* footprint = editFrame.CreateNewFootprint( msg );
419 
420  footprint->SetFPID( LIB_ID( wxEmptyString, wxT( "mw_inductor" ) ) );
423  footprint->SetPosition( aInductorPattern.m_End );
424 
425  // Generate segments
426  for( unsigned jj = 1; jj < buffer.size(); jj++ )
427  {
428  FP_SHAPE* seg;
429  seg = new FP_SHAPE( footprint );
430  seg->SetStart( buffer[jj - 1] );
431  seg->SetEnd( buffer[jj] );
432  seg->SetWidth( aInductorPattern.m_Width );
433  seg->SetLayer( footprint->GetLayer() );
434  seg->SetShape( S_SEGMENT );
435  seg->SetStart0( seg->GetStart() - footprint->GetPosition() );
436  seg->SetEnd0( seg->GetEnd() - footprint->GetPosition() );
437  footprint->Add( seg );
438  }
439 
440  // Place a pad on each end of coil.
441  pad = new PAD( footprint );
442 
443  footprint->Add( pad );
444 
445  pad->SetName( "1" );
446  pad->SetPosition( aInductorPattern.m_End );
447  pad->SetPos0( pad->GetPosition() - footprint->GetPosition() );
448 
449  pad->SetSize( wxSize( aInductorPattern.m_Width, aInductorPattern.m_Width ) );
450 
451  pad->SetLayerSet( LSET( footprint->GetLayer() ) );
453  pad->SetShape( PAD_SHAPE_CIRCLE );
454 
455  PAD* newpad = new PAD( *pad );
456  const_cast<KIID&>( newpad->m_Uuid ) = KIID();
457 
458  footprint->Add( newpad );
459 
460  pad = newpad;
461  pad->SetName( "2" );
462  pad->SetPosition( aInductorPattern.m_Start );
463  pad->SetPos0( pad->GetPosition() - footprint->GetPosition() );
464 
465  // Modify text positions.
466  wxPoint refPos( ( aInductorPattern.m_Start.x + aInductorPattern.m_End.x ) / 2,
467  ( aInductorPattern.m_Start.y + aInductorPattern.m_End.y ) / 2 );
468 
469  wxPoint valPos = refPos;
470 
471  refPos.y -= footprint->Reference().GetTextSize().y;
472  footprint->Reference().SetPosition( refPos );
473  valPos.y += footprint->Value().GetTextSize().y;
474  footprint->Value().SetPosition( valPos );
475 
476  return footprint;
477 }
double EuclideanNorm(const wxPoint &vector)
Euclidean norm of a 2D vector.
Definition: trigo.h:148
int sign(T val)
Definition: util.h:101
void SetEnd0(const wxPoint &aPoint)
Definition: fp_shape.h:114
virtual void SetPosition(const wxPoint &aPos) override
Definition: fp_text.h:93
BOARD * board() const
const wxPoint & GetEnd() const
Function GetEnd returns the ending point of the graphic.
Definition: pcb_shape.h:156
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition: board_item.h:194
Implementation of conversion functions that require both schematic and board internal units.
This file is part of the common library.
COMMIT & Add(EDA_ITEM *aItem)
Notify observers that aItem has been added.
Definition: commit.h:78
A KICAD version of wxTextEntryDialog which supports the various improvments/work-arounds from DIALOG_...
TOOL_MANAGER * m_toolMgr
Definition: tool_base.h:215
void SetTextValidator(wxTextValidatorStyle style)
Smd pad, appears on the solder paste layer (default)
Definition: pad_shapes.h:81
FOOTPRINT * createMicrowaveInductor(MICROWAVE_INDUCTOR_PATTERN &aPattern, wxString &aErrorMessage)
Create an S-shaped coil footprint for microwave applications.
void SetName(const wxString &aName)
Set the pad name (sometimes called pad number, although it can be an array reference like AA12).
Definition: pad.h:132
bool RunAction(const std::string &aActionName, bool aNow=false, T aParam=NULL)
Run the specified action.
Definition: tool_manager.h:141
usual segment : line with rounded ends
Definition: board_item.h:50
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition: board.h:591
void SetSize(const wxSize &aSize)
Definition: pad.h:231
void RotatePoint(int *pX, int *pY, double angle)
Definition: trigo.cpp:228
A logical library item identifier and consists of various portions much like a URI.
Definition: lib_id.h:51
void SetAttributes(int aAttributes)
Definition: footprint.h:232
FP_TEXT & Value()
read/write accessors:
Definition: footprint.h:459
This class provides a custom wxValidator object for limiting the allowable characters when defining f...
Definition: validators.h:63
FP_TEXT & Reference()
Definition: footprint.h:460
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)
Draw a microwave inductor interactively.
Definition: kiid.h:44
INDUCTOR_S_SHAPE_RESULT
LSET is a set of PCB_LAYER_IDs.
void SetPos0(const wxPoint &aPos)
Definition: pad.h:225
void SetShape(PCB_SHAPE_TYPE_T aShape)
Definition: pcb_shape.h:129
long long int ValueFromString(EDA_UNITS aUnits, const wxString &aTextValue, EDA_DATA_TYPE aType)
Function ValueFromString converts aTextValue in aUnits to internal units used by the application.
Definition: base_units.cpp:451
int ShowQuasiModal()
const wxSize & GetTextSize() const
Definition: eda_text.h:245
const wxPoint & GetStart() const
Function GetStart returns the starting point of the graphic.
Definition: pcb_shape.h:145
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 ...
FOOTPRINT * footprint() const
a few functions useful in geometry calculations.
void SetShape(PAD_SHAPE_T aShape)
Set the new shape of this pad.
Definition: pad.h:160
void SetStart0(const wxPoint &aPoint)
Definition: fp_shape.h:111
const KIID m_Uuid
Definition: eda_item.h:524
FOOTPRINT * CreateNewFootprint(const wxString &aFootprintName)
Creates a new footprint, at position 0,0.
void SetFPID(const LIB_ID &aFPID)
Definition: footprint.h:191
void SetLayerSet(LSET aLayers) override
Definition: pad.h:359
#define ADJUST_SIZE
wxPoint GetPosition() const override
Definition: pad.h:177
void SetStart(const wxPoint &aStart)
Definition: pcb_shape.h:148
#define _(s)
Definition: 3d_actions.cpp:33
static DIRECTION_45::AngleType angle(const VECTOR2I &a, const VECTOR2I &b)
void SetPosition(const wxPoint &aPos) override
Definition: pad.h:171
static TOOL_ACTION selectItem
Select an item (specified as the event parameter).
Definition: pcb_actions.h:66
The main frame for Pcbnew.
void SetWidth(int aWidth)
Definition: pcb_shape.h:117
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
Revert the commit by restoring the modified items state.
wxPoint GetPosition() const override
Definition: footprint.h:182
void ShowInfoBarError(const wxString &aErrorMsg, bool aShowCloseButton=false)
Show the WX_INFOBAR displayed on the top of the canvas with a message and an error icon on the left o...
void ClearFlags(STATUS_FLAGS aMask=EDA_ITEM_ALL_FLAGS)
Definition: eda_item.h:203
wxString StringFromValue(EDA_UNITS aUnits, double aValue, bool aAddUnitSymbol, EDA_DATA_TYPE aType)
Convert a value to a string using double notation.
Definition: base_units.cpp:225
double ArcTangente(int dy, int dx)
Definition: trigo.cpp:182
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT) override
Removes an item from the container.
Definition: footprint.cpp:467
Definition: pad.h:60
void SetPosition(const wxPoint &aPos) override
Definition: footprint.cpp:1437
Requested length too long.
virtual PCB_LAYER_ID GetLayer() const
Return the primary layer this item is on.
Definition: board_item.h:173
int GetArcToSegmentCount(int aRadius, int aErrorMax, double aArcAngleDegree)
Custom text control validator definitions.
EDA_UNITS GetUserUnits() const
Return the user units currently in use.
void SetEnd(const wxPoint &aEnd)
Definition: pcb_shape.h:159
Parameters for construction of a microwave inductor.
void SetAttribute(PAD_ATTR_T aAttribute)
Definition: pcbnew/pad.cpp:560