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 <fp_shape.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 = 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 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  editFrame.ShowInfoBarError( 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 );
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  FP_SHAPE* seg;
428  seg = new FP_SHAPE( module );
429  seg->SetStart( buffer[jj - 1] );
430  seg->SetEnd( buffer[jj] );
431  seg->SetWidth( aInductorPattern.m_Width );
432  seg->SetLayer( module->GetLayer() );
433  seg->SetShape( S_SEGMENT );
434  seg->SetStart0( seg->GetStart() - module->GetPosition() );
435  seg->SetEnd0( seg->GetEnd() - module->GetPosition() );
436  module->Add( seg );
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  const_cast<KIID&>( newpad->m_Uuid ) = KIID();
456 
457  module->Add( newpad );
458 
459  pad = newpad;
460  pad->SetName( "2" );
461  pad->SetPosition( aInductorPattern.m_Start );
462  pad->SetPos0( pad->GetPosition() - module->GetPosition() );
463 
464  // Modify text positions.
465  wxPoint refPos( ( aInductorPattern.m_Start.x + aInductorPattern.m_End.x ) / 2,
466  ( aInductorPattern.m_Start.y + aInductorPattern.m_End.y ) / 2 );
467 
468  wxPoint valPos = refPos;
469 
470  refPos.y -= module->Reference().GetTextSize().y;
471  module->Reference().SetPosition( refPos );
472  valPos.y += module->Value().GetTextSize().y;
473  module->Value().SetPosition( valPos );
474 
476  return module;
477 }
double EuclideanNorm(const wxPoint &vector)
Euclidean norm of a 2D vector.
Definition: trigo.h:134
MODULE * createMicrowaveInductor(MICROWAVE_INDUCTOR_PATTERN &aPattern, wxString &aErrorMessage)
Creates an S-shaped coil footprint for microwave applications.
int sign(T val)
Definition: util.h:101
usual segment : line with rounded ends
void SetEnd0(const wxPoint &aPoint)
Definition: fp_shape.h:108
virtual void SetPosition(const wxPoint &aPos) override
Definition: fp_text.h:95
BOARD * board() const
int GetCurrentTrackWidth() const
Function GetCurrentTrackWidth.
const wxPoint & GetEnd() const
Function GetEnd returns the ending point of the graphic.
Definition: pcb_shape.h:140
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:172
COMMIT & Add(EDA_ITEM *aItem)
Adds a new item to the model
Definition: commit.h:78
FP_TEXT & Value()
read/write accessors:
Definition: class_module.h:475
void CalculateBoundingBox()
Function CalculateBoundingBox calculates the bounding box in board coordinates.
A KICAD version of wxTextEntryDialog which supports the various improvments/work-arounds from DIALOG_...
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
bool RunAction(const std::string &aActionName, bool aNow=false, T aParam=NULL)
Function RunAction() Runs the specified action.
Definition: tool_manager.h:141
void SetPosition(const wxPoint &aPos) override
Definition: class_pad.h:166
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition: class_board.h:558
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...
FP_TEXT & Reference()
Definition: class_module.h:476
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
Definition: kiid.h:44
INDUCTOR_S_SHAPE_RESULT
LSET is a set of PCB_LAYER_IDs.
void SetShape(PCB_SHAPE_TYPE_T aShape)
Definition: pcb_shape.h:113
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:444
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
void SetPos0(const wxPoint &aPos)
Definition: class_pad.h:224
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:129
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:230
a few functions useful in geometry calculations.
void SetPosition(const wxPoint &aPos) override
void ShowInfoBarError(const wxString &aErrorMsg)
void SetAttribute(PAD_ATTR_T aAttribute)
Definition: class_pad.cpp:512
void SetStart0(const wxPoint &aPoint)
Definition: fp_shape.h:105
Pad object description.
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT) override
const KIID m_Uuid
Definition: eda_item.h:151
#define ADJUST_SIZE
void SetStart(const wxPoint &aStart)
Definition: pcb_shape.h:132
MODULE * module() const
#define _(s)
Definition: 3d_actions.cpp:33
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
PCB_EDIT_FRAME is the main frame for Pcbnew.
void SetWidth(int aWidth)
Definition: pcb_shape.h:99
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:155
void ClearFlags(STATUS_FLAGS aMask=EDA_ITEM_ALL_FLAGS)
Definition: eda_item.h:222
wxString StringFromValue(EDA_UNITS aUnits, double aValue, bool aAddUnitSymbol, EDA_DATA_TYPE aType)
Function StringFromValue returns the string from aValue according to units (inch, mm ....
Definition: base_units.cpp:220
wxPoint GetPosition() const override
Definition: class_module.h:201
double ArcTangente(int dy, int dx)
Definition: trigo.cpp:162
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:211
void SetLayerSet(LSET aLayers) override
Definition: class_pad.h:348
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:143
Parameters for construction of a microwave inductor.
void SetAttributes(int aAttributes)
Definition: class_module.h:252