KiCad PCB EDA Suite
Loading...
Searching...
No Matches
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 The 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>
27#include <pad.h>
28#include <pcb_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
50static void gen_arc( std::vector<VECTOR2I>& aBuffer, const VECTOR2I& aStartPoint, const VECTOR2I& aCenter,
51 const EDA_ANGLE& a_ArcAngle )
52{
53 VECTOR2D first_point = VECTOR2D( aStartPoint ) - aCenter;
54 double radius = first_point.EuclideanNorm();
55 int seg_count = GetArcToSegmentCount( radius, ARC_HIGH_DEF, a_ArcAngle );
56
57 double increment_angle = a_ArcAngle.AsRadians() / seg_count;
58
59 // Creates nb_seg point to approximate arc by segments:
60 for( int ii = 1; ii <= seg_count; ii++ )
61 {
62 double rot_angle = increment_angle * ii;
63 double fcos = cos( rot_angle );
64 double fsin = sin( rot_angle );
65 VECTOR2I currpt;
66
67 // Rotate current point:
68 currpt.x = KiROUND( ( first_point.x * fcos + first_point.y * fsin ) );
69 currpt.y = KiROUND( ( first_point.y * fcos - first_point.x * fsin ) );
70
71 auto corner = aCenter + currpt;
72 aBuffer.push_back( corner );
73 }
74}
75
76
78{
79 OK,
80 TOO_LONG,
81 TOO_SHORT,
82 NO_REPR,
83};
84
85
95static INDUCTOR_S_SHAPE_RESULT BuildCornersList_S_Shape( std::vector<VECTOR2I>& aBuffer,
96 const VECTOR2I& aStartPoint, const VECTOR2I& aEndPoint,
97 int aLength, int aWidth )
98{
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 EDA_ANGLE angle( pt );
143 int min_len = pt.EuclideanNorm();
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 angle = -angle;
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, -ANGLE_90 );
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 if( ii & 1 ) // odd order arcs are greater than 0
252 sign = -1;
253 else
254 sign = 1;
255
256 centre = pt;
257 centre.y += radius;
258 gen_arc( aBuffer, pt, centre, ANGLE_180 * sign );
259 pt = aBuffer.back();
260 pt.x += segm_len * sign;
261 aBuffer.push_back( pt );
262 }
263
264 // The last point is false:
265 // it is the end of a full size segment, but must be
266 // the end of the second half_size segment. Change it.
267 sign *= -1;
268 aBuffer.back().x = aStartPoint.x + radius * sign;
269
270 // create last arc
271 pt = aBuffer.back();
272 centre = pt;
273 centre.y += radius;
274 gen_arc( aBuffer, pt, centre, ANGLE_90 * sign );
275
276 // Rotate point
277 angle += ANGLE_90;
278
279 for( unsigned jj = 0; jj < aBuffer.size(); jj++ )
280 RotatePoint( aBuffer[jj], aStartPoint, angle );
281
282 // push last point (end point)
283 aBuffer.push_back( aEndPoint );
284
286}
287
288
290{
291 PCB_EDIT_FRAME& editFrame = *getEditFrame<PCB_EDIT_FRAME>();
292
294
296
297 pattern.m_Start = { aStart.x, aStart.y };
298 pattern.m_End = { aEnd.x, aEnd.y };
299
300 wxString errorMessage;
301
302 auto inductorFP = std::unique_ptr<FOOTPRINT>( createMicrowaveInductor( pattern, errorMessage ) );
303
304 // on any error, report if we can
305 if ( !inductorFP || !errorMessage.IsEmpty() )
306 {
307 if ( !errorMessage.IsEmpty() )
308 editFrame.ShowInfoBarError( errorMessage );
309 }
310 else
311 {
312 // at this point, we can save the footprint
313 m_toolMgr->RunAction<EDA_ITEM*>( ACTIONS::selectItem, inductorFP.get() );
314
315 BOARD_COMMIT commit( this );
316 commit.Add( inductorFP.release() );
317 commit.Push( _("Add Microwave Inductor" ) );
318 }
319}
320
321
323 wxString& aErrorMessage )
324{
325 /* Build a microwave inductor footprint.
326 * - Length Mself.lng
327 * - Extremities Mself.m_Start and Mself.m_End
328 * We must determine:
329 * Mself.nbrin = number of segments perpendicular to the direction
330 * (The coil nbrin will demicercles + 1 + 2 1 / 4 circle)
331 * Mself.lbrin = length of a strand
332 * Mself.radius = radius of rounded parts of the coil
333 * Mself.delta = segments extremities connection between him and the coil even
334 *
335 * The equations are
336 * Mself.m_Size.x = 2 * Mself.radius + Mself.lbrin
337 * Mself.m_Size.y * Mself.delta = 2 + 2 * Mself.nbrin * Mself.radius
338 * Mself.lng = 2 * Mself.delta / / connections to the coil
339 + (Mself.nbrin-2) * Mself.lbrin / / length of the strands except 1st and last
340 + (Mself.nbrin 1) * (PI * Mself.radius) / / length of rounded
341 * Mself.lbrin + / 2 - Melf.radius * 2) / / length of 1st and last bit
342 *
343 * The constraints are:
344 * Nbrin >= 2
345 * Mself.radius < Mself.m_Size.x
346 * Mself.m_Size.y = Mself.radius * 4 + 2 * Mself.raccord
347 * Mself.lbrin> Mself.radius * 2
348 *
349 * The calculation is conducted in the following way:
350 * Initially:
351 * Nbrin = 2
352 * Radius = 4 * m_Size.x (arbitrarily fixed value)
353 * Then:
354 * Increasing the number of segments to the desired length
355 * (Radius decreases if necessary)
356 */
357
358 PAD* pad;
359 PCB_EDIT_FRAME* editFrame = getEditFrame<PCB_EDIT_FRAME>();
360
361 VECTOR2I pt = aInductorPattern.m_End - aInductorPattern.m_Start;
362 int min_len = pt.EuclideanNorm();
363 aInductorPattern.m_Length = min_len;
364
365 // Enter the desired length.
366 wxString msg = editFrame->StringFromValue( aInductorPattern.m_Length );
367 WX_TEXT_ENTRY_DIALOG dlg( editFrame, _( "Length of track:" ), _( "Create Microwave Footprint" ), msg );
368
369 // TODO: why is this QuasiModal?
370 if( dlg.ShowQuasiModal() != wxID_OK )
371 return nullptr; // canceled by user
372
373 aInductorPattern.m_Length = editFrame->ValueFromString( dlg.GetValue() );
374
375 // Control values (ii = minimum length)
376 if( aInductorPattern.m_Length < min_len )
377 {
378 aErrorMessage = _( "Requested length < minimum length" );
379 return nullptr;
380 }
381
382 // Calculate the elements.
383 std::vector<VECTOR2I> buffer;
384 const INDUCTOR_S_SHAPE_RESULT res = BuildCornersList_S_Shape( buffer, aInductorPattern.m_Start,
385 aInductorPattern.m_End,
386 aInductorPattern.m_Length,
387 aInductorPattern.m_Width );
388
389 switch( res )
390 {
391 case INDUCTOR_S_SHAPE_RESULT::TOO_LONG:
392 aErrorMessage = _( "Requested length too large" );
393 return nullptr;
394 case INDUCTOR_S_SHAPE_RESULT::TOO_SHORT:
395 aErrorMessage = _( "Requested length too small" );
396 return nullptr;
397 case INDUCTOR_S_SHAPE_RESULT::NO_REPR:
398 aErrorMessage = _( "Requested length can't be represented" );
399 return nullptr;
400 case INDUCTOR_S_SHAPE_RESULT::OK:
401 break;
402 }
403
404 // Generate footprint. the value is also used as footprint name.
405 msg = wxT( "L" );
406 WX_TEXT_ENTRY_DIALOG cmpdlg( editFrame, _( "Component value:" ), _( "Create Microwave Footprint" ), msg );
408
409 // TODO: why is this QuasiModal?
410 if( ( cmpdlg.ShowQuasiModal() != wxID_OK ) || msg.IsEmpty() )
411 return nullptr; // Aborted by user
412
413 FOOTPRINT* footprint = editFrame->CreateNewFootprint( msg, wxEmptyString );
414
415 footprint->SetFPID( LIB_ID( wxEmptyString, wxT( "mw_inductor" ) ) );
418 footprint->SetPosition( aInductorPattern.m_End );
419
420 // Generate segments
421 for( unsigned jj = 1; jj < buffer.size(); jj++ )
422 {
423 PCB_SHAPE* seg = new PCB_SHAPE( footprint, SHAPE_T::SEGMENT );
424 seg->SetStart( buffer[jj - 1] );
425 seg->SetEnd( buffer[jj] );
426 seg->SetStroke( STROKE_PARAMS( aInductorPattern.m_Width, LINE_STYLE::SOLID ) );
427 seg->SetLayer( footprint->GetLayer() );
428 footprint->Add( seg );
429 }
430
431 // Place a pad on each end of coil.
432 pad = new PAD( footprint );
433
434 footprint->Add( pad );
435
436 pad->SetNumber( wxT( "1" ) );
437 pad->SetPosition( aInductorPattern.m_End );
438
439 pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( aInductorPattern.m_Width, aInductorPattern.m_Width ) );
440
441 pad->SetLayerSet( LSET( { footprint->GetLayer() } ) );
442 pad->SetAttribute( PAD_ATTRIB::SMD );
443 pad->SetShape( PADSTACK::ALL_LAYERS, PAD_SHAPE::CIRCLE );
444
445 PAD* newpad = new PAD( *pad );
446 const_cast<KIID&>( newpad->m_Uuid ) = KIID();
447
448 footprint->Add( newpad );
449
450 pad = newpad;
451 pad->SetNumber( wxT( "2" ) );
452 pad->SetPosition( aInductorPattern.m_Start );
453
454 // Modify text positions.
455 VECTOR2I refPos( ( aInductorPattern.m_Start.x + aInductorPattern.m_End.x ) / 2,
456 ( aInductorPattern.m_Start.y + aInductorPattern.m_End.y ) / 2 );
457
458 VECTOR2I valPos = refPos;
459
460 refPos.y -= footprint->Reference().GetTextSize().y;
461 footprint->Reference().SetPosition( refPos );
462 valPos.y += footprint->Value().GetTextSize().y;
463 footprint->Value().SetPosition( valPos );
464
465 return footprint;
466}
constexpr int ARC_HIGH_DEF
Definition: base_units.h:129
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition: box2.h:990
static TOOL_ACTION selectItem
Select an item (specified as the event parameter).
Definition: actions.h:224
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition: board.cpp:1024
int ShowQuasiModal()
double AsRadians() const
Definition: eda_angle.h:120
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...
A base class for most all the KiCad significant classes used in schematics and boards.
Definition: eda_item.h:98
const KIID m_Uuid
Definition: eda_item.h:516
void ClearFlags(EDA_ITEM_FLAGS aMask=EDA_ITEM_ALL_FLAGS)
Definition: eda_item.h:144
void SetStart(const VECTOR2I &aStart)
Definition: eda_shape.h:177
void SetEnd(const VECTOR2I &aEnd)
Definition: eda_shape.h:219
VECTOR2I GetTextSize() const
Definition: eda_text.h:258
Provide a custom wxValidator object for limiting the allowable characters when defining footprint nam...
Definition: validators.h:53
void SetPosition(const VECTOR2I &aPos) override
Definition: footprint.cpp:2513
void SetFPID(const LIB_ID &aFPID)
Definition: footprint.h:252
void SetAttributes(int aAttributes)
Definition: footprint.h:294
PCB_FIELD & Value()
read/write accessors:
Definition: footprint.h:663
PCB_LAYER_ID GetLayer() const override
Return the primary layer this item is on.
Definition: footprint.h:239
PCB_FIELD & Reference()
Definition: footprint.h:664
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
Definition: footprint.cpp:1080
Definition: kiid.h:49
A logical library item identifier and consists of various portions much like a URI.
Definition: lib_id.h:49
LSET is a set of PCB_LAYER_IDs.
Definition: lset.h:37
FOOTPRINT * createMicrowaveInductor(MICROWAVE_INDUCTOR_PATTERN &aPattern, wxString &aErrorMessage)
Create an S-shaped coil footprint for microwave applications.
void createInductorBetween(const VECTOR2I &aStart, const VECTOR2I &aEnd)
Draw a microwave inductor interactively.
static constexpr PCB_LAYER_ID ALL_LAYERS
! Temporary layer identifier to identify code that is not padstack-aware
Definition: padstack.h:145
Definition: pad.h:54
FOOTPRINT * CreateNewFootprint(wxString aFootprintName, const wxString &aLibName)
Create a new footprint at position 0,0.
The main frame for Pcbnew.
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
Definition: pcb_shape.cpp:176
void SetStroke(const STROKE_PARAMS &aStroke) override
Definition: pcb_shape.h:92
virtual void SetPosition(const VECTOR2I &aPos) override
Definition: pcb_text.h:89
BOARD * board() const
FOOTPRINT * footprint() const
Simple container to manage line stroke parameters.
Definition: stroke_params.h:94
TOOL_MANAGER * m_toolMgr
Definition: tool_base.h:220
bool RunAction(const std::string &aActionName, T aParam)
Run the specified action immediately, pausing the current action to run the new one.
Definition: tool_manager.h:150
wxString StringFromValue(double aValue, bool aAddUnitLabel=false, EDA_DATA_TYPE aType=EDA_DATA_TYPE::DISTANCE) const
Converts aValue in internal units into a united string.
int ValueFromString(const wxString &aTextValue, EDA_DATA_TYPE aType=EDA_DATA_TYPE::DISTANCE) const
Converts aTextValue in aUnits to internal units used by the frame.
T EuclideanNorm() const
Compute the Euclidean norm of the vector, which is defined as sqrt(x ** 2 + y ** 2).
Definition: vector2d.h:283
A KICAD version of wxTextEntryDialog which supports the various improvements/work-arounds from DIALOG...
wxString GetValue() const
void SetTextValidator(const wxTextValidator &validator)
This file is part of the common library.
#define _(s)
static constexpr EDA_ANGLE ANGLE_90
Definition: eda_angle.h:413
static constexpr EDA_ANGLE ANGLE_180
Definition: eda_angle.h:415
@ FP_EXCLUDE_FROM_POS_FILES
Definition: footprint.h:82
@ FP_EXCLUDE_FROM_BOM
Definition: footprint.h:83
a few functions useful in geometry calculations.
int GetArcToSegmentCount(int aRadius, int aErrorMax, const EDA_ANGLE &aArcAngle)
static INDUCTOR_S_SHAPE_RESULT BuildCornersList_S_Shape(std::vector< VECTOR2I > &aBuffer, const VECTOR2I &aStartPoint, const VECTOR2I &aEndPoint, int aLength, int aWidth)
Function BuildCornersList_S_Shape Create a path like a S-shaped coil.
static void gen_arc(std::vector< VECTOR2I > &aBuffer, const VECTOR2I &aStartPoint, const VECTOR2I &aCenter, const EDA_ANGLE &a_ArcAngle)
Function gen_arc generates an arc using arc approximation by lines: Center aCenter Angle "angle" (in ...
INDUCTOR_S_SHAPE_RESULT
@ TOO_SHORT
Requested length too long.
@ TOO_LONG
S-shape constructed.
@ NO_REPR
Requested length too short.
#define ADJUST_SIZE
Parameters for construction of a microwave inductor.
VECTOR3I res
int radius
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition: trigo.cpp:229
constexpr int sign(T val)
Definition: util.h:145
Custom text control validator definitions.
VECTOR2< int32_t > VECTOR2I
Definition: vector2d.h:695
VECTOR2< double > VECTOR2D
Definition: vector2d.h:694