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 <base_units.h>
25 #include <board_commit.h>
26 #include <board_design_settings.h>
27 #include <pad.h>
28 #include <fp_shape.h>
29 #include <footprint.h>
30 #include <confirm.h>
33 #include <math/util.h> // for KiROUND
35 #include <tool/tool_manager.h>
36 #include <tools/pcb_actions.h>
37 #include <pcb_edit_frame.h>
38 #include <validators.h>
39 
50 static void gen_arc( std::vector <wxPoint>& aBuffer,
51  const wxPoint& aStartPoint,
52  const wxPoint& aCenter,
53  int a_ArcAngle )
54 {
55  auto first_point = aStartPoint - aCenter;
56  auto radius = KiROUND( EuclideanNorm( first_point ) );
57  int seg_count = GetArcToSegmentCount( radius, ARC_HIGH_DEF, a_ArcAngle / 10.0 );
58 
59  double increment_angle = (double) a_ArcAngle * M_PI / 1800 / seg_count;
60 
61  // Creates nb_seg point to approximate arc by segments:
62  for( int ii = 1; ii <= seg_count; ii++ )
63  {
64  double rot_angle = increment_angle * ii;
65  double fcos = cos( rot_angle );
66  double fsin = sin( rot_angle );
67  wxPoint currpt;
68 
69  // Rotate current point:
70  currpt.x = KiROUND( ( first_point.x * fcos + first_point.y * fsin ) );
71  currpt.y = KiROUND( ( first_point.y * fcos - first_point.x * fsin ) );
72 
73  auto corner = aCenter + currpt;
74  aBuffer.push_back( corner );
75  }
76 }
77 
78 
80 {
81  OK,
82  TOO_LONG,
83  TOO_SHORT,
84  NO_REPR,
85 };
86 
87 
97 static INDUCTOR_S_SHAPE_RESULT BuildCornersList_S_Shape( std::vector<wxPoint>& aBuffer,
98  const wxPoint& aStartPoint, const wxPoint& aEndPoint, int aLength, int aWidth )
99 {
100 /* We must determine:
101  * segm_count = number of segments perpendicular to the direction
102  * segm_len = length of a strand
103  * radius = radius of rounded parts of the coil
104  * stubs_len = length of the 2 stubs( segments parallel to the direction)
105  * connecting the start point to the start point of the S shape
106  * and the ending point to the end point of the S shape
107  * The equations are (assuming the area size of the entire shape is Size:
108  * Size.x = 2 * radius + segm_len
109  * Size.y = (segm_count + 2 ) * 2 * radius + 2 * stubs_len
110  * aInductorPattern.m_length = 2 * delta // connections to the coil
111  * + (segm_count-2) * segm_len // length of the strands except 1st and last
112  * + (segm_count) * (PI * radius) // length of rounded
113  * segm_len + / 2 - radius * 2) // length of 1st and last bit
114  *
115  * The constraints are:
116  * segm_count >= 2
117  * radius < m_Size.x
118  * Size.y = (radius * 4) + (2 * stubs_len)
119  * segm_len > radius * 2
120  *
121  * The calculation is conducted in the following way:
122  * first:
123  * segm_count = 2
124  * radius = 4 * Size.x (arbitrarily fixed value)
125  * Then:
126  * Increasing the number of segments to the desired length
127  * (radius decreases if necessary)
128  */
129  wxPoint size;
130 
131  // This scale factor adjusts the arc length to handle
132  // the arc to segment approximation.
133  // because we use SEGM_COUNT_PER_360DEG segment to approximate a circle,
134  // the trace len must be corrected when calculated using arcs
135  // this factor adjust calculations and must be changed if SEGM_COUNT_PER_360DEG is modified
136  // because trace using segment is shorter the corresponding arc
137  // ADJUST_SIZE is the ratio between tline len and the arc len for an arc
138  // of 360/ADJUST_SIZE angle
139  #define ADJUST_SIZE 0.988
140 
141  auto pt = aEndPoint - aStartPoint;
142  double angle = -ArcTangente( pt.y, pt.x );
143  int min_len = KiROUND( EuclideanNorm( pt ) );
144  int segm_len = 0; // length of segments
145  int full_len; // full len of shape (sum of length of all segments + arcs)
146 
147 
148  /* Note: calculations are made for a vertical coil (more easy calculations)
149  * and after points are rotated to their actual position
150  * So the main direction is the Y axis.
151  * the 2 stubs are on the Y axis
152  * the others segments are parallel to the X axis.
153  */
154 
155  // Calculate the size of area (for a vertical shape)
156  size.x = min_len / 2;
157  size.y = min_len;
158 
159  // Choose a reasonable starting value for the radius of the arcs.
160  int radius = std::min( aWidth * 5, size.x / 4 );
161 
162  int segm_count; // number of full len segments
163  // the half size segments (first and last segment) are not counted here
164  int stubs_len = 0; // length of first or last segment (half size of others segments)
165 
166  for( segm_count = 0; ; segm_count++ )
167  {
168  stubs_len = ( size.y - ( radius * 2 * (segm_count + 2 ) ) ) / 2;
169 
170  if( stubs_len < size.y / 10 ) // Reduce radius.
171  {
172  stubs_len = size.y / 10;
173  radius = ( size.y - (2 * stubs_len) ) / ( 2 * (segm_count + 2) );
174 
175  if( radius < aWidth ) // Radius too small.
176  {
177  // Unable to create line: Requested length value is too large for room
179  }
180  }
181 
182  segm_len = size.x - ( radius * 2 );
183  full_len = 2 * stubs_len; // Length of coil connections.
184  full_len += segm_len * segm_count; // Length of full length segments.
185  full_len += KiROUND( ( segm_count + 2 ) * M_PI * ADJUST_SIZE * radius ); // Ard arcs len
186  full_len += segm_len - (2 * radius); // Length of first and last segments
187  // (half size segments len = segm_len/2 - radius).
188 
189  if( full_len >= aLength )
190  break;
191  }
192 
193  // Adjust len by adjusting segm_len:
194  int delta_size = full_len - aLength;
195 
196  // reduce len of the segm_count segments + 2 half size segments (= 1 full size segment)
197  segm_len -= delta_size / (segm_count + 1);
198 
199  // at this point, it could still be that the requested length is too
200  // short (because 4 quarter-circles are too long)
201  // to fix this is a relatively complex numerical problem which probably
202  // needs a refactor in this area. For now, just reject these cases:
203  {
204  const int min_total_length = 2 * stubs_len + 2 * M_PI * ADJUST_SIZE * radius;
205  if( min_total_length > aLength )
206  {
207  // we can't express this inductor with 90-deg arcs of this radius
209  }
210  }
211 
212  if( segm_len - 2 * radius < 0 )
213  {
214  // we can't represent this exact requested length with this number
215  // of segments (using the current algorithm). This stems from when
216  // you add a segment, you also add another half-circle, so there's a
217  // little bit of "dead" space.
218  // It's a bit ugly to just reject the input, as it might be possible
219  // to tweak the radius, but, again, that probably needs a refactor.
221  }
222 
223  // Generate first line (the first stub) and first arc (90 deg arc)
224  pt = aStartPoint;
225  aBuffer.push_back( pt );
226  pt.y += stubs_len;
227  aBuffer.push_back( pt );
228 
229  auto centre = pt;
230  centre.x -= radius;
231  gen_arc( aBuffer, pt, centre, -900 );
232  pt = aBuffer.back();
233 
234  int half_size_seg_len = segm_len / 2 - radius;
235 
236  if( half_size_seg_len )
237  {
238  pt.x -= half_size_seg_len;
239  aBuffer.push_back( pt );
240  }
241 
242  // Create shape.
243  int ii;
244  int sign = 1;
245  segm_count += 1; // increase segm_count to create the last half_size segment
246 
247  for( ii = 0; ii < segm_count; ii++ )
248  {
249  int arc_angle;
250 
251  if( ii & 1 ) // odd order arcs are greater than 0
252  sign = -1;
253  else
254  sign = 1;
255 
256  arc_angle = 1800 * sign;
257  centre = pt;
258  centre.y += radius;
259  gen_arc( aBuffer, pt, centre, arc_angle );
260  pt = aBuffer.back();
261  pt.x += segm_len * sign;
262  aBuffer.push_back( pt );
263  }
264 
265  // The last point is false:
266  // it is the end of a full size segment, but must be
267  // the end of the second half_size segment. Change it.
268  sign *= -1;
269  aBuffer.back().x = aStartPoint.x + radius * sign;
270 
271  // create last arc
272  pt = aBuffer.back();
273  centre = pt;
274  centre.y += radius;
275  gen_arc( aBuffer, pt, centre, 900 * sign );
276 
277  // Rotate point
278  angle += 900;
279 
280  for( unsigned jj = 0; jj < aBuffer.size(); jj++ )
281  {
282  RotatePoint( &aBuffer[jj], aStartPoint, angle );
283  }
284 
285  // push last point (end point)
286  aBuffer.push_back( aEndPoint );
287 
289 }
290 
291 
292 void MICROWAVE_TOOL::createInductorBetween( const VECTOR2I& aStart, const VECTOR2I& aEnd )
293 {
294  PCB_EDIT_FRAME& editFrame = *getEditFrame<PCB_EDIT_FRAME>();
295 
297 
299 
300  pattern.m_Start = { aStart.x, aStart.y };
301  pattern.m_End = { aEnd.x, aEnd.y };
302 
303  wxString errorMessage;
304 
305  auto inductorFP = std::unique_ptr<FOOTPRINT>( createMicrowaveInductor( pattern, errorMessage ) );
306 
307  // on any error, report if we can
308  if ( !inductorFP || !errorMessage.IsEmpty() )
309  {
310  if ( !errorMessage.IsEmpty() )
311  editFrame.ShowInfoBarError( errorMessage );
312  }
313  else
314  {
315  // at this point, we can save the footprint
316  m_toolMgr->RunAction( PCB_ACTIONS::selectItem, true, inductorFP.get() );
317 
318  BOARD_COMMIT commit( this );
319  commit.Add( inductorFP.release() );
320  commit.Push( _("Add microwave inductor" ) );
321  }
322 }
323 
324 
326  wxString& aErrorMessage )
327 {
328  /* Build a microwave inductor footprint.
329  * - Length Mself.lng
330  * - Extremities Mself.m_Start and Mself.m_End
331  * We must determine:
332  * Mself.nbrin = number of segments perpendicular to the direction
333  * (The coil nbrin will demicercles + 1 + 2 1 / 4 circle)
334  * Mself.lbrin = length of a strand
335  * Mself.radius = radius of rounded parts of the coil
336  * Mself.delta = segments extremities connection between him and the coil even
337  *
338  * The equations are
339  * Mself.m_Size.x = 2 * Mself.radius + Mself.lbrin
340  * Mself.m_Size.y * Mself.delta = 2 + 2 * Mself.nbrin * Mself.radius
341  * Mself.lng = 2 * Mself.delta / / connections to the coil
342  + (Mself.nbrin-2) * Mself.lbrin / / length of the strands except 1st and last
343  + (Mself.nbrin 1) * (PI * Mself.radius) / / length of rounded
344  * Mself.lbrin + / 2 - Melf.radius * 2) / / length of 1st and last bit
345  *
346  * The constraints are:
347  * Nbrin >= 2
348  * Mself.radius < Mself.m_Size.x
349  * Mself.m_Size.y = Mself.radius * 4 + 2 * Mself.raccord
350  * Mself.lbrin> Mself.radius * 2
351  *
352  * The calculation is conducted in the following way:
353  * Initially:
354  * Nbrin = 2
355  * Radius = 4 * m_Size.x (arbitrarily fixed value)
356  * Then:
357  * Increasing the number of segments to the desired length
358  * (Radius decreases if necessary)
359  */
360 
361  PAD* pad;
362  wxString msg;
363 
364  PCB_EDIT_FRAME& editFrame = *getEditFrame<PCB_EDIT_FRAME>();
365 
366  auto pt = aInductorPattern.m_End - aInductorPattern.m_Start;
367  int min_len = KiROUND( EuclideanNorm( pt ) );
368  aInductorPattern.m_Length = min_len;
369 
370  // Enter the desired length.
371  msg = StringFromValue( editFrame.GetUserUnits(), aInductorPattern.m_Length );
372  WX_TEXT_ENTRY_DIALOG dlg( &editFrame, _( "Length of Trace:" ), wxEmptyString, msg );
373 
374  if( dlg.ShowQuasiModal() != wxID_OK )
375  return nullptr; // canceled by user
376 
377  msg = dlg.GetValue();
378  aInductorPattern.m_Length = ValueFromString( editFrame.GetUserUnits(), msg );
379 
380  // Control values (ii = minimum length)
381  if( aInductorPattern.m_Length < min_len )
382  {
383  aErrorMessage = _( "Requested length < minimum length" );
384  return nullptr;
385  }
386 
387  // Calculate the elements.
388  std::vector <wxPoint> buffer;
389  const INDUCTOR_S_SHAPE_RESULT res = BuildCornersList_S_Shape( buffer, aInductorPattern.m_Start,
390  aInductorPattern.m_End,
391  aInductorPattern.m_Length,
392  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 );
412  cmpdlg.SetTextValidator( FOOTPRINT_NAME_VALIDATOR( &msg ) );
413 
414  if( ( cmpdlg.ShowQuasiModal() != wxID_OK ) || msg.IsEmpty() )
415  return nullptr; // Aborted by user
416 
417  FOOTPRINT* footprint = editFrame.CreateNewFootprint( msg );
418 
419  footprint->SetFPID( LIB_ID( wxEmptyString, wxT( "mw_inductor" ) ) );
422  footprint->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( footprint );
429  seg->SetStart( buffer[jj - 1] );
430  seg->SetEnd( buffer[jj] );
431  seg->SetWidth( aInductorPattern.m_Width );
432  seg->SetLayer( footprint->GetLayer() );
433  seg->SetShape( SHAPE_T::SEGMENT );
434  seg->SetStart0( seg->GetStart() - footprint->GetPosition() );
435  seg->SetEnd0( seg->GetEnd() - footprint->GetPosition() );
436  footprint->Add( seg );
437  }
438 
439  // Place a pad on each end of coil.
440  pad = new PAD( footprint );
441 
442  footprint->Add( pad );
443 
444  pad->SetNumber( "1" );
445  pad->SetPosition( aInductorPattern.m_End );
446  pad->SetPos0( pad->GetPosition() - footprint->GetPosition() );
447 
448  pad->SetSize( wxSize( aInductorPattern.m_Width, aInductorPattern.m_Width ) );
449 
450  pad->SetLayerSet( LSET( footprint->GetLayer() ) );
451  pad->SetAttribute( PAD_ATTRIB::SMD );
452  pad->SetShape( PAD_SHAPE::CIRCLE );
453 
454  PAD* newpad = new PAD( *pad );
455  const_cast<KIID&>( newpad->m_Uuid ) = KIID();
456 
457  footprint->Add( newpad );
458 
459  pad = newpad;
460  pad->SetNumber( "2" );
461  pad->SetPosition( aInductorPattern.m_Start );
462  pad->SetPos0( pad->GetPosition() - footprint->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 -= footprint->Reference().GetTextSize().y;
471  footprint->Reference().SetPosition( refPos );
472  valPos.y += footprint->Value().GetTextSize().y;
473  footprint->Value().SetPosition( valPos );
474 
475  return footprint;
476 }
double EuclideanNorm(const wxPoint &vector)
Euclidean norm of a 2D vector.
Definition: trigo.h:146
int sign(T val)
Definition: util.h:104
void SetEnd0(const wxPoint &aPoint)
Definition: fp_shape.h:114
virtual void SetPosition(const wxPoint &aPos) override
Definition: fp_text.h:93
void SetShape(SHAPE_T aShape)
Definition: pcb_shape.h:109
BOARD * board() const
const wxPoint & GetEnd() const
Return the ending point of the graphic.
Definition: pcb_shape.h:134
virtual void SetLayer(PCB_LAYER_ID aLayer)
Set the layer this item is on.
Definition: board_item.h:192
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 improvements/work-arounds from DIALOG...
TOOL_MANAGER * m_toolMgr
Definition: tool_base.h:214
void SetTextValidator(wxTextValidatorStyle style)
Smd pad, appears on the solder paste layer (default)
FOOTPRINT * createMicrowaveInductor(MICROWAVE_INDUCTOR_PATTERN &aPattern, wxString &aErrorMessage)
Create an S-shaped coil footprint for microwave applications.
usual segment : line with rounded ends
bool RunAction(const std::string &aActionName, bool aNow=false, T aParam=NULL)
Run the specified action.
Definition: tool_manager.h:143
void RotatePoint(int *pX, int *pY, double angle)
Definition: trigo.cpp:229
A logical library item identifier and consists of various portions much like a URI.
Definition: lib_id.h:51
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition: board.cpp:589
void SetAttributes(int aAttributes)
Definition: footprint.h:236
FP_TEXT & Value()
read/write accessors:
Definition: footprint.h:466
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:467
void ShowInfoBarError(const wxString &aErrorMsg, bool aShowCloseButton=false, WX_INFOBAR::MESSAGE_TYPE aType=WX_INFOBAR::MESSAGE_TYPE::GENERIC)
Show the WX_INFOBAR displayed on the top of the canvas with a message and an error icon on the left o...
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.
Definition: layer_ids.h:502
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:416
int ShowQuasiModal()
const wxSize & GetTextSize() const
Definition: eda_text.h:238
const wxPoint & GetStart() const
Return the starting point of the graphic.
Definition: pcb_shape.h:124
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
#define _(s)
void ClearFlags(EDA_ITEM_FLAGS aMask=EDA_ITEM_ALL_FLAGS)
Definition: eda_item.h:154
a few functions useful in geometry calculations.
void SetStart0(const wxPoint &aPoint)
Definition: fp_shape.h:111
const KIID m_Uuid
Definition: eda_item.h:475
FOOTPRINT * CreateNewFootprint(const wxString &aFootprintName)
Creates a new footprint, at position 0,0.
void SetFPID(const LIB_ID &aFPID)
Definition: footprint.h:195
#define ADJUST_SIZE
void SetStart(const wxPoint &aStart)
Definition: pcb_shape.h:127
static DIRECTION_45::AngleType angle(const VECTOR2I &a, const VECTOR2I &b)
static TOOL_ACTION selectItem
Select an item (specified as the event parameter).
Definition: pcb_actions.h:62
The main frame for Pcbnew.
void SetWidth(int aWidth)
Definition: pcb_shape.h:96
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:73
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:186
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:204
double ArcTangente(int dy, int dx)
Definition: trigo.cpp:183
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT) override
Removes an item from the container.
Definition: footprint.cpp:513
Definition: pad.h:57
void SetPosition(const wxPoint &aPos) override
Definition: footprint.cpp:1499
Requested length too long.
virtual PCB_LAYER_ID GetLayer() const
Return the primary layer this item is on.
Definition: board_item.h:171
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:137
Parameters for construction of a microwave inductor.