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, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <base_units.h>
21#include <board_commit.h>
23#include <pad.h>
24#include <pcb_shape.h>
25#include <footprint.h>
26#include <confirm.h>
29#include <math/util.h> // for KiROUND
31#include <tool/tool_manager.h>
32#include <tools/pcb_actions.h>
33#include <pcb_edit_frame.h>
34#include <validators.h>
35
46static void gen_arc( std::vector<VECTOR2I>& aBuffer, const VECTOR2I& aStartPoint, const VECTOR2I& aCenter,
47 const EDA_ANGLE& a_ArcAngle )
48{
49 VECTOR2D first_point = VECTOR2D( aStartPoint ) - aCenter;
50 double radius = first_point.EuclideanNorm();
51 int seg_count = GetArcToSegmentCount( radius, ARC_HIGH_DEF, a_ArcAngle );
52
53 double increment_angle = a_ArcAngle.AsRadians() / seg_count;
54
55 // Creates nb_seg point to approximate arc by segments:
56 for( int ii = 1; ii <= seg_count; ii++ )
57 {
58 double rot_angle = increment_angle * ii;
59 double fcos = cos( rot_angle );
60 double fsin = sin( rot_angle );
61 VECTOR2I currpt;
62
63 // Rotate current point:
64 currpt.x = KiROUND( ( first_point.x * fcos + first_point.y * fsin ) );
65 currpt.y = KiROUND( ( first_point.y * fcos - first_point.x * fsin ) );
66
67 auto corner = aCenter + currpt;
68 aBuffer.push_back( corner );
69 }
70}
71
72
80
81
91static INDUCTOR_S_SHAPE_RESULT BuildCornersList_S_Shape( std::vector<VECTOR2I>& aBuffer,
92 const VECTOR2I& aStartPoint, const VECTOR2I& aEndPoint,
93 int aLength, int aWidth )
94{
95 /*
96 * We must determine:
97 * segm_count = number of segments perpendicular to the direction
98 * segm_len = length of a strand
99 * radius = radius of rounded parts of the coil
100 * stubs_len = length of the 2 stubs( segments parallel to the direction)
101 * connecting the start point to the start point of the S shape
102 * and the ending point to the end point of the S shape
103 * The equations are (assuming the area size of the entire shape is Size:
104 * Size.x = 2 * radius + segm_len
105 * Size.y = (segm_count + 2 ) * 2 * radius + 2 * stubs_len
106 * aInductorPattern.m_length = 2 * delta // connections to the coil
107 * + (segm_count-2) * segm_len // length of the strands except 1st and last
108 * + (segm_count) * (PI * radius) // length of rounded
109 * segm_len + / 2 - radius * 2) // length of 1st and last bit
110 *
111 * The constraints are:
112 * segm_count >= 2
113 * radius < m_Size.x
114 * Size.y = (radius * 4) + (2 * stubs_len)
115 * segm_len > radius * 2
116 *
117 * The calculation is conducted in the following way:
118 * first:
119 * segm_count = 2
120 * radius = 4 * Size.x (arbitrarily fixed value)
121 * Then:
122 * Increasing the number of segments to the desired length
123 * (radius decreases if necessary)
124 */
125 wxPoint size;
126
127 // This scale factor adjusts the arc length to handle
128 // the arc to segment approximation.
129 // because we use SEGM_COUNT_PER_360DEG segment to approximate a circle,
130 // the trace len must be corrected when calculated using arcs
131 // this factor adjust calculations and must be changed if SEGM_COUNT_PER_360DEG is modified
132 // because trace using segment is shorter the corresponding arc
133 // ADJUST_SIZE is the ratio between tline len and the arc len for an arc
134 // of 360/ADJUST_SIZE angle
135 #define ADJUST_SIZE 0.988
136
137 auto pt = aEndPoint - aStartPoint;
138 EDA_ANGLE angle( pt );
139 int min_len = pt.EuclideanNorm();
140 int segm_len = 0; // length of segments
141 int full_len; // full len of shape (sum of length of all segments + arcs)
142
143 angle = -angle;
144
145 /*
146 * Note: calculations are made for a vertical coil (more easy calculations)
147 * and after points are rotated to their actual position
148 * So the main direction is the Y axis.
149 * the 2 stubs are on the Y axis
150 * the others segments are parallel to the X axis.
151 */
152
153 // Calculate the size of area (for a vertical shape)
154 size.x = min_len / 2;
155 size.y = min_len;
156
157 // Choose a reasonable starting value for the radius of the arcs.
158 int radius = std::min( aWidth * 5, size.x / 4 );
159
160 int segm_count; // number of full len segments
161 // the half size segments (first and last segment) are not counted here
162 int stubs_len = 0; // length of first or last segment (half size of others segments)
163
164 for( segm_count = 0; ; segm_count++ )
165 {
166 stubs_len = ( size.y - ( radius * 2 * (segm_count + 2 ) ) ) / 2;
167
168 if( stubs_len < size.y / 10 ) // Reduce radius.
169 {
170 stubs_len = size.y / 10;
171 radius = ( size.y - (2 * stubs_len) ) / ( 2 * (segm_count + 2) );
172
173 if( radius < aWidth ) // Radius too small.
174 {
175 // Unable to create line: Requested length value is too large for room
177 }
178 }
179
180 segm_len = size.x - ( radius * 2 );
181 full_len = 2 * stubs_len; // Length of coil connections.
182 full_len += segm_len * segm_count; // Length of full length segments.
183 full_len += KiROUND( ( segm_count + 2 ) * M_PI * ADJUST_SIZE * radius ); // Ard arcs len
184 full_len += segm_len - (2 * radius); // Length of first and last segments
185 // (half size segments len = segm_len/2 - radius).
186
187 if( full_len >= aLength )
188 break;
189 }
190
191 // Adjust len by adjusting segm_len:
192 int delta_size = full_len - aLength;
193
194 // reduce len of the segm_count segments + 2 half size segments (= 1 full size segment)
195 segm_len -= delta_size / (segm_count + 1);
196
197 // at this point, it could still be that the requested length is too
198 // short (because 4 quarter-circles are too long)
199 // to fix this is a relatively complex numerical problem which probably
200 // needs a refactor in this area. For now, just reject these cases:
201 {
202 const int min_total_length = 2 * stubs_len + 2 * M_PI * ADJUST_SIZE * radius;
203 if( min_total_length > aLength )
204 {
205 // we can't express this inductor with 90-deg arcs of this radius
207 }
208 }
209
210 if( segm_len - 2 * radius < 0 )
211 {
212 // we can't represent this exact requested length with this number
213 // of segments (using the current algorithm). This stems from when
214 // you add a segment, you also add another half-circle, so there's a
215 // little bit of "dead" space.
216 // It's a bit ugly to just reject the input, as it might be possible
217 // to tweak the radius, but, again, that probably needs a refactor.
219 }
220
221 // Generate first line (the first stub) and first arc (90 deg arc)
222 pt = aStartPoint;
223 aBuffer.push_back( pt );
224 pt.y += stubs_len;
225 aBuffer.push_back( pt );
226
227 auto centre = pt;
228 centre.x -= radius;
229 gen_arc( aBuffer, pt, centre, -ANGLE_90 );
230 pt = aBuffer.back();
231
232 int half_size_seg_len = segm_len / 2 - radius;
233
234 if( half_size_seg_len )
235 {
236 pt.x -= half_size_seg_len;
237 aBuffer.push_back( pt );
238 }
239
240 // Create shape.
241 int ii;
242 int sign = 1;
243 segm_count += 1; // increase segm_count to create the last half_size segment
244
245 for( ii = 0; ii < segm_count; ii++ )
246 {
247 if( ii & 1 ) // odd order arcs are greater than 0
248 sign = -1;
249 else
250 sign = 1;
251
252 centre = pt;
253 centre.y += radius;
254 gen_arc( aBuffer, pt, centre, ANGLE_180 * sign );
255 pt = aBuffer.back();
256 pt.x += segm_len * sign;
257 aBuffer.push_back( pt );
258 }
259
260 // The last point is false:
261 // it is the end of a full size segment, but must be
262 // the end of the second half_size segment. Change it.
263 sign *= -1;
264 aBuffer.back().x = aStartPoint.x + radius * sign;
265
266 // create last arc
267 pt = aBuffer.back();
268 centre = pt;
269 centre.y += radius;
270 gen_arc( aBuffer, pt, centre, ANGLE_90 * sign );
271
272 // Rotate point
273 angle += ANGLE_90;
274
275 for( unsigned jj = 0; jj < aBuffer.size(); jj++ )
276 RotatePoint( aBuffer[jj], aStartPoint, angle );
277
278 // push last point (end point)
279 aBuffer.push_back( aEndPoint );
280
282}
283
284
286{
288
290
292
293 pattern.m_Start = { aStart.x, aStart.y };
294 pattern.m_End = { aEnd.x, aEnd.y };
295
296 wxString errorMessage;
297
298 auto inductorFP = std::unique_ptr<FOOTPRINT>( createMicrowaveInductor( pattern, errorMessage ) );
299
300 // on any error, report if we can
301 if ( !inductorFP || !errorMessage.IsEmpty() )
302 {
303 if ( !errorMessage.IsEmpty() )
304 editFrame.ShowInfoBarError( errorMessage );
305 }
306 else
307 {
308 // at this point, we can save the footprint
309 m_toolMgr->RunAction<EDA_ITEM*>( ACTIONS::selectItem, inductorFP.get() );
310
311 BOARD_COMMIT commit( this );
312 commit.Add( inductorFP.release() );
313 commit.Push( _("Add Microwave Inductor" ) );
314 }
315}
316
317
319 wxString& aErrorMessage )
320{
321 /* Build a microwave inductor footprint.
322 * - Length Mself.lng
323 * - Extremities Mself.m_Start and Mself.m_End
324 * We must determine:
325 * Mself.nbrin = number of segments perpendicular to the direction
326 * (The coil nbrin will demicercles + 1 + 2 1 / 4 circle)
327 * Mself.lbrin = length of a strand
328 * Mself.radius = radius of rounded parts of the coil
329 * Mself.delta = segments extremities connection between him and the coil even
330 *
331 * The equations are
332 * Mself.m_Size.x = 2 * Mself.radius + Mself.lbrin
333 * Mself.m_Size.y * Mself.delta = 2 + 2 * Mself.nbrin * Mself.radius
334 * Mself.lng = 2 * Mself.delta / / connections to the coil
335 + (Mself.nbrin-2) * Mself.lbrin / / length of the strands except 1st and last
336 + (Mself.nbrin 1) * (PI * Mself.radius) / / length of rounded
337 * Mself.lbrin + / 2 - Melf.radius * 2) / / length of 1st and last bit
338 *
339 * The constraints are:
340 * Nbrin >= 2
341 * Mself.radius < Mself.m_Size.x
342 * Mself.m_Size.y = Mself.radius * 4 + 2 * Mself.raccord
343 * Mself.lbrin> Mself.radius * 2
344 *
345 * The calculation is conducted in the following way:
346 * Initially:
347 * Nbrin = 2
348 * Radius = 4 * m_Size.x (arbitrarily fixed value)
349 * Then:
350 * Increasing the number of segments to the desired length
351 * (Radius decreases if necessary)
352 */
353
354 PAD* pad;
356
357 VECTOR2I pt = aInductorPattern.m_End - aInductorPattern.m_Start;
358 int min_len = pt.EuclideanNorm();
359 aInductorPattern.m_Length = min_len;
360
361 // Enter the desired length.
362 wxString msg = editFrame->StringFromValue( aInductorPattern.m_Length );
363 WX_TEXT_ENTRY_DIALOG dlg( editFrame, _( "Length of track:" ), _( "Create Microwave Footprint" ), msg );
364
365 // TODO: why is this QuasiModal?
366 if( dlg.ShowQuasiModal() != wxID_OK )
367 return nullptr; // canceled by user
368
369 aInductorPattern.m_Length = editFrame->ValueFromString( dlg.GetValue() );
370
371 // Control values (ii = minimum length)
372 if( aInductorPattern.m_Length < min_len )
373 {
374 aErrorMessage = _( "Requested length < minimum length" );
375 return nullptr;
376 }
377
378 // Calculate the elements.
379 std::vector<VECTOR2I> buffer;
380 const INDUCTOR_S_SHAPE_RESULT res = BuildCornersList_S_Shape( buffer, aInductorPattern.m_Start,
381 aInductorPattern.m_End,
382 aInductorPattern.m_Length,
383 aInductorPattern.m_Width );
384
385 switch( res )
386 {
388 aErrorMessage = _( "Requested length too large" );
389 return nullptr;
391 aErrorMessage = _( "Requested length too small" );
392 return nullptr;
394 aErrorMessage = _( "Requested length can't be represented" );
395 return nullptr;
397 break;
398 }
399
400 // Generate footprint. the value is also used as footprint name.
401 msg = wxT( "L" );
402 WX_TEXT_ENTRY_DIALOG cmpdlg( editFrame, _( "Component value:" ), _( "Create Microwave Footprint" ), msg );
404
405 // TODO: why is this QuasiModal?
406 if( ( cmpdlg.ShowQuasiModal() != wxID_OK ) || msg.IsEmpty() )
407 return nullptr; // Aborted by user
408
409 FOOTPRINT* footprint = editFrame->CreateNewFootprint( msg, wxEmptyString );
410
411 footprint->SetFPID( LIB_ID( wxEmptyString, wxT( "mw_inductor" ) ) );
413 footprint->ClearFlags();
414 footprint->SetPosition( aInductorPattern.m_End );
415
416 // Generate segments
417 for( unsigned jj = 1; jj < buffer.size(); jj++ )
418 {
420 seg->SetStart( buffer[jj - 1] );
421 seg->SetEnd( buffer[jj] );
422 seg->SetStroke( STROKE_PARAMS( aInductorPattern.m_Width, LINE_STYLE::SOLID ) );
423 seg->SetLayer( footprint->GetLayer() );
424 footprint->Add( seg );
425 }
426
427 // Place a pad on each end of coil.
428 pad = new PAD( footprint );
429
430 footprint->Add( pad );
431
432 pad->SetNumber( wxT( "1" ) );
433 pad->SetPosition( aInductorPattern.m_End );
434
435 pad->SetSize( PADSTACK::ALL_LAYERS, VECTOR2I( aInductorPattern.m_Width, aInductorPattern.m_Width ) );
436
437 pad->SetLayerSet( LSET( { footprint->GetLayer() } ) );
438 pad->SetAttribute( PAD_ATTRIB::SMD );
440
441 PAD* newpad = new PAD( *pad );
442 newpad->ResetUuidDirect();
443
444 footprint->Add( newpad );
445
446 pad = newpad;
447 pad->SetNumber( wxT( "2" ) );
448 pad->SetPosition( aInductorPattern.m_Start );
449
450 // Modify text positions.
451 VECTOR2I refPos( ( aInductorPattern.m_Start.x + aInductorPattern.m_End.x ) / 2,
452 ( aInductorPattern.m_Start.y + aInductorPattern.m_End.y ) / 2 );
453
454 VECTOR2I valPos = refPos;
455
456 refPos.y -= footprint->Reference().GetTextSize().y;
457 footprint->Reference().SetPosition( refPos );
458 valPos.y += footprint->Value().GetTextSize().y;
459 footprint->Value().SetPosition( valPos );
460
461 return footprint;
462}
constexpr int ARC_HIGH_DEF
Definition base_units.h:137
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:986
static TOOL_ACTION selectItem
Select an item (specified as the event parameter).
Definition actions.h:223
virtual void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
void ResetUuidDirect()
Definition board_item.h:242
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1149
COMMIT & Add(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Add a new item to the model.
Definition commit.h:74
double AsRadians() const
Definition eda_angle.h:120
void ShowInfoBarError(const wxString &aErrorMsg, bool aShowCloseButton=false, INFOBAR_MESSAGE_TYPE aType=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:96
Provide a custom wxValidator object for limiting the allowable characters when defining footprint nam...
Definition validators.h:49
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
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:177
Definition pad.h:61
FOOTPRINT * CreateNewFootprint(wxString aFootprintName, const wxString &aLibName)
Create a new footprint at position 0,0.
The main frame for Pcbnew.
void SetEnd(const VECTOR2I &aEnd) override
void SetLayer(PCB_LAYER_ID aLayer) override
Set the layer this item is on.
void SetStart(const VECTOR2I &aStart) override
void SetStroke(const STROKE_PARAMS &aStroke) override
BOARD * board() const
FOOTPRINT * footprint() const
Simple container to manage line stroke parameters.
T * getEditFrame() const
Return the application window object, casted to requested user type.
Definition tool_base.h:182
TOOL_MANAGER * m_toolMgr
Definition tool_base.h:220
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:279
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
@ SEGMENT
Definition eda_shape.h:46
#define OK
@ FP_EXCLUDE_FROM_POS_FILES
Definition footprint.h:85
@ FP_EXCLUDE_FROM_BOM
Definition footprint.h:86
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
@ SMD
Smd pad, appears on the solder paste layer (default)
Definition padstack.h:99
Parameters for construction of a microwave inductor.
VECTOR3I res
int radius
#define M_PI
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:225
constexpr int sign(T val)
Definition util.h:141
Custom text control validator definitions.
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
VECTOR2< double > VECTOR2D
Definition vector2d.h:682