7
mirror of https://gitlab.com/kicad/code/kicad.git synced 2025-04-11 09:40:09 +00:00

Dogbones: add slots for acute angles

It's actually not as hard as I thought, though
surely has wierd edge cases which users might need to
handle manually.
This commit is contained in:
John Beard 2024-09-22 13:45:19 +01:00
parent 17143e252f
commit 95beedb612
12 changed files with 479 additions and 60 deletions

View File

@ -336,6 +336,7 @@ set( COMMON_DLG_SRCS
dialogs/dialog_import_choose_project_base.cpp
dialogs/dialog_locked_items_query.cpp
dialogs/dialog_locked_items_query_base.cpp
dialogs/dialog_multi_unit_entry.cpp
dialogs/dialog_page_settings_base.cpp
dialogs/dialog_paste_special.cpp
dialogs/dialog_paste_special_base.cpp

View File

@ -0,0 +1,176 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include "dialogs/dialog_multi_unit_entry.h"
#include <eda_draw_frame.h>
#include <widgets/unit_binder.h>
#include <core/type_helpers.h>
#include <wx/button.h>
#include <wx/checkbox.h>
#include <wx/gbsizer.h>
#include <wx/stattext.h>
WX_MULTI_ENTRY_DIALOG::WX_MULTI_ENTRY_DIALOG( EDA_DRAW_FRAME* aParent, const wxString& aCaption,
std::vector<ENTRY> aEntries ) :
DIALOG_SHIM( aParent, wxID_ANY, aCaption ), m_entries( std::move( aEntries ) )
{
SetSizeHints( wxDefaultSize, wxDefaultSize );
wxBoxSizer* bSizerMain = new wxBoxSizer( wxVERTICAL );
wxGridBagSizer* bSizerContent;
bSizerContent = new wxGridBagSizer( 0, 0 );
bSizerContent->SetFlexibleDirection( wxHORIZONTAL );
bSizerContent->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_ALL );
bSizerMain->Add( bSizerContent, 1, wxEXPAND | wxRIGHT | wxLEFT, 5 );
int gbRow = 0;
for( const ENTRY& entry : m_entries )
{
std::visit(
[&]( const auto& aValue )
{
using EntryType = std::decay_t<decltype( aValue )>;
if constexpr( std::is_same_v<EntryType, UNIT_BOUND> )
{
// Label / Entry / Unit
// and a binder
wxStaticText* label =
new wxStaticText( this, wxID_ANY, entry.m_label, wxDefaultPosition,
wxDefaultSize, 0 );
label->Wrap( -1 );
bSizerContent->Add( label, wxGBPosition( gbRow, 0 ), wxGBSpan( 1, 1 ),
wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM | wxLEFT,
5 );
wxTextCtrl* textCtrl =
new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition,
wxDefaultSize, 0 );
bSizerContent->Add( textCtrl, wxGBPosition( gbRow, 1 ), wxGBSpan( 1, 1 ),
wxALIGN_CENTER_VERTICAL | wxALL | wxEXPAND, 5 );
wxStaticText* unit_label = new wxStaticText(
this, wxID_ANY, _( "unit" ), wxDefaultPosition, wxDefaultSize, 0 );
unit_label->Wrap( -1 );
bSizerContent->Add( unit_label, wxGBPosition( gbRow, 2 ), wxGBSpan( 1, 1 ),
wxTOP | wxBOTTOM | wxRIGHT | wxALIGN_CENTER_VERTICAL,
5 );
if( !entry.m_tooltip.IsEmpty() )
textCtrl->SetToolTip( entry.m_tooltip );
m_controls.push_back( textCtrl );
m_unit_binders.push_back( std::make_unique<UNIT_BINDER>(
aParent, label, textCtrl, unit_label ) );
m_unit_binders.back()->SetValue( aValue.m_default );
}
else if constexpr( std::is_same_v<EntryType, CHECKBOX> )
{
// Checkbox across all 3 cols
wxCheckBox* checkBox =
new wxCheckBox( this, wxID_ANY, wxEmptyString, wxDefaultPosition,
wxDefaultSize, 0 );
bSizerContent->Add( checkBox, wxGBPosition( gbRow, 0 ), wxGBSpan( 1, 3 ),
wxALIGN_CENTER_VERTICAL | wxALL, 5 );
checkBox->SetLabel( entry.m_label );
checkBox->SetValue( aValue.m_default );
if( !entry.m_tooltip.IsEmpty() )
checkBox->SetToolTip( entry.m_tooltip );
m_controls.push_back( checkBox );
m_unit_binders.push_back( nullptr );
}
else
{
static_assert( always_false<EntryType>::value, "non-exhaustive visitor" );
}
},
entry.m_value );
gbRow++;
}
// Grow the value column (now it knows it has a col 1)
bSizerContent->AddGrowableCol( 1 );
wxStdDialogButtonSizer* sdbSizer1 = new wxStdDialogButtonSizer();
wxButton* sdbSizer1OK = new wxButton( this, wxID_OK );
sdbSizer1->AddButton( sdbSizer1OK );
wxButton* sdbSizer1Cancel = new wxButton( this, wxID_CANCEL );
sdbSizer1->AddButton( sdbSizer1Cancel );
sdbSizer1->Realize();
bSizerMain->Add( sdbSizer1, 0, wxALL | wxEXPAND, 5 );
SetSizer( bSizerMain );
SetupStandardButtons();
Layout();
// Now all widgets have the size fixed, call FinishDialogSettings
finishDialogSettings();
}
std::vector<WX_MULTI_ENTRY_DIALOG::RESULT> WX_MULTI_ENTRY_DIALOG::GetValues() const
{
std::vector<RESULT> results;
for( size_t ii = 0; ii < m_entries.size(); ++ii )
{
wxWindow* const control = m_controls[ii];
// Visit the value definitons to look up the right control type
std::visit(
[&]( const auto& aValueDef )
{
using ArgType = std::decay_t<decltype( aValueDef )>;
if constexpr( std::is_same_v<ArgType, UNIT_BOUND> )
{
UNIT_BINDER* binder = m_unit_binders[ii].get();
wxASSERT( binder );
results.push_back( binder ? binder->GetValue() : 0 );
}
else if constexpr( std::is_same_v<ArgType, CHECKBOX> )
{
wxCheckBox* checkBox = static_cast<wxCheckBox*>( control );
results.push_back( checkBox->GetValue() );
}
else
{
static_assert( always_false<ArgType>::value, "non-exhaustive visitor" );
}
},
m_entries[ii].m_value );
}
return results;
}

View File

@ -0,0 +1,96 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#pragma once
#include <memory>
#include <variant>
#include <dialog_shim.h>
class EDA_DRAW_FRAME;
class UNIT_BINDER;
/**
* A dialog like @ref WX_UNIT_ENTRY_DIALOG, but with multiple entries.
*
* You can give a list of entries, each with a label and a default value.
* The control type will be chosen based on the type of the default value.
*/
class WX_MULTI_ENTRY_DIALOG : public DIALOG_SHIM
{
public:
struct UNIT_BOUND
{
long long int m_default;
};
struct CHECKBOX
{
bool m_default;
};
/**
* The type of the entry value
*
* - long long int: for a unit-bound value
* - bool: for a checkbox
*/
using TYPE = std::variant<UNIT_BOUND, CHECKBOX>;
struct ENTRY
{
wxString m_label;
TYPE m_value;
wxString m_tooltip;
};
/**
* Corresponding result type for each entry type
*/
using RESULT = std::variant<long long int, bool>;
/**
* Create a multi-entry dialog
*
* @param aParent The parent frame
* @param aCaption The dialog caption (title)
* @param aEntries The list of entries
*/
WX_MULTI_ENTRY_DIALOG( EDA_DRAW_FRAME* aParent, const wxString& aCaption,
std::vector<ENTRY> aEntries );
/**
* Returns the values in the order they were added.
*
* The type of the values will be the same as the default value type
* for the ENTRY that defined this value.
*/
std::vector<RESULT> GetValues() const;
private:
std::vector<ENTRY> m_entries;
std::vector<wxWindow*> m_controls;
std::vector<std::unique_ptr<UNIT_BINDER>> m_unit_binders;
};

View File

@ -22,21 +22,16 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#pragma once
#include <dialogs/dialog_unit_entry_base.h>
#include <widgets/unit_binder.h>
/**
* An extension of WX_TEXT_ENTRY_DIALOG that uses UNIT_BINDER to request a dimension
* (e.g. mm, inches, etc) from the user according to the selected units
*/
#ifndef _DIALOG_UNIT_ENTRY_H_
#define _DIALOG_UNIT_ENTRY_H_
#include <widgets/unit_binder.h>
#include "../../common/dialogs/dialog_unit_entry_base.h"
class WX_UNIT_ENTRY_DIALOG : public WX_UNIT_ENTRY_DIALOG_BASE
{
public:
@ -68,5 +63,3 @@ private:
UNIT_BINDER m_unit_binder_x;
UNIT_BINDER m_unit_binder_y;
};
#endif // _DIALOG_UNIT_ENTRY_H_

View File

@ -85,4 +85,4 @@ struct DOGBONE_RESULT
* | |
*/
std::optional<DOGBONE_RESULT> ComputeDogbone( const SEG& aSegA, const SEG& aSegB,
int aDogboneRadius );
int aDogboneRadius, bool aAddSlots );

View File

@ -60,6 +60,12 @@ SEG NormalisedSeg( const SEG& aSeg );
*/
const VECTOR2I& GetOtherEnd( const SEG& aSeg, const VECTOR2I& aPoint );
/**
* Get the shared endpoint of two segments, if it exists, or std::nullopt
* if the segments are not connected end-to-end.
*/
OPT_VECTOR2I GetSharedEndpoint( const SEG& aSegA, const SEG& aSegB );
/**
* Decompose a BOX2 into four segments.
*

View File

@ -61,6 +61,19 @@ bool PointIsInDirection( const VECTOR2<T>& aPoint, const VECTOR2<T>& aDirection,
}
/**
* Check that the two given points are in the same direction from some other point.
*
* I.e. the vectors from aFrom to aPointA and aFrom to aPointB are within 90 degrees.
*/
template <typename T>
bool PointsAreInSameDirection( const VECTOR2<T>& aPointA, const VECTOR2<T>& aPointB,
const VECTOR2<T>& aFrom )
{
return PointIsInDirection( aPointB, aPointA - aFrom, aFrom );
}
/**
* Determine if a segment's vector is within 90 degrees of a given direction.
*/

View File

@ -24,31 +24,15 @@
#include "geometry/corner_operations.h"
#include <geometry/circle.h>
#include <geometry/half_line.h>
#include <geometry/shape_arc.h>
#include <geometry/shape_utils.h>
#include <geometry/vector_utils.h>
#include <trigo.h>
namespace
{
/**
* Get the shared endpoint of two segments, if it exists, or std::nullopt
* if the segments are not connected end-to-end.
*/
std::optional<VECTOR2I> GetSharedEndpoint( const SEG& aSegA, const SEG& aSegB )
{
if( aSegA.A == aSegB.A || aSegA.A == aSegB.B )
{
return aSegA.A;
}
else if( aSegA.B == aSegB.A || aSegA.B == aSegB.B )
{
return aSegA.B;
}
return std::nullopt;
}
/**
* Get the bisector of two segments that join at a corner.
*/
@ -57,7 +41,7 @@ SEG GetBisectorOfCornerSegments( const SEG& aSegA, const SEG& aSegB, int aLength
// Use the "parallelogram" method to find the bisector
// The intersection point of the two lines is the one that is shared by both segments
const std::optional<VECTOR2I> corner = GetSharedEndpoint( aSegA, aSegB );
const std::optional<VECTOR2I> corner = KIGEOM::GetSharedEndpoint( aSegA, aSegB );
// Get the vector of a segment pointing away from a point
const auto getSegVectorPointingAwayFrom = []( const SEG& aSeg,
@ -106,7 +90,7 @@ std::optional<CHAMFER_RESULT> ComputeChamferPoints( const SEG& aSegA, const SEG&
// otherwise we would need to decide which inside corner to chamfer
// Figure out which end points are the ones at the intersection
const std::optional<VECTOR2I> corner = GetSharedEndpoint( aSegA, aSegB );
const std::optional<VECTOR2I> corner = KIGEOM::GetSharedEndpoint( aSegA, aSegB );
if( !corner )
{
@ -143,9 +127,9 @@ std::optional<CHAMFER_RESULT> ComputeChamferPoints( const SEG& aSegA, const SEG&
std::optional<DOGBONE_RESULT> ComputeDogbone( const SEG& aSegA, const SEG& aSegB,
int aDogboneRadius )
int aDogboneRadius, bool aAddSlots )
{
const std::optional<VECTOR2I> corner = GetSharedEndpoint( aSegA, aSegB );
const std::optional<VECTOR2I> corner = KIGEOM::GetSharedEndpoint( aSegA, aSegB );
// Cannot handle parallel lines
if( !corner || aSegA.Angle( aSegB ).IsHorizontal() )
@ -198,19 +182,77 @@ std::optional<DOGBONE_RESULT> ComputeDogbone( const SEG& aSegA, const SEG& aSegB
const VECTOR2I& aOtherPtA = KIGEOM::GetOtherEnd( aSegA, *corner );
const VECTOR2I& aOtherPtB = KIGEOM::GetOtherEnd( aSegB, *corner );
// See if we need to update the original segments
// or if the dogbone consumed them
std::optional<SEG> new_a, new_b;
if( aOtherPtA != *ptOnSegA )
new_a = SEG{ aOtherPtA, *ptOnSegA };
const EDA_ANGLE angle_epsilon( 1e-3, EDA_ANGLE_T::DEGREES_T );
const bool small_arc_mouth = std::abs( arc.GetCentralAngle() ) > ( ANGLE_180 + angle_epsilon );
if( aOtherPtB != *ptOnSegB )
new_b = SEG{ aOtherPtB, *ptOnSegB };
{
// See if we need to update the original segments
// or if the dogbone consumed them
std::optional<SEG> new_a, new_b;
if( aOtherPtA != *ptOnSegA )
new_a = SEG{ aOtherPtA, *ptOnSegA };
const EDA_ANGLE epsilon( 1e-5, EDA_ANGLE_T::DEGREES_T );
const bool small_arc_mouth = arc.GetCentralAngle().Normalize() > ( ANGLE_180 + epsilon );
if( aOtherPtB != *ptOnSegB )
new_b = SEG{ aOtherPtB, *ptOnSegB };
return DOGBONE_RESULT{
arc, new_a, new_b, small_arc_mouth,
// Nice and easy
if( !small_arc_mouth || !aAddSlots )
{
return DOGBONE_RESULT{
arc,
new_a,
new_b,
small_arc_mouth,
};
}
}
// If it's a small mouth, we can try to work out the minimal slot to allow
// First the arc will be pulled back to 180 degrees
SHAPE_ARC slotArc = SHAPE_ARC( GetRotated( *corner, dogboneCenter, ANGLE_90 ), *corner,
GetRotated( *corner, dogboneCenter, -ANGLE_90 ), 0 );
// Make sure P0 is still the 'A' end
if( !KIGEOM::PointsAreInSameDirection( slotArc.GetP0(), arc.GetP0(), dogboneCenter ) )
{
slotArc.Reverse();
}
// Take the bisector and glue it to the arc ends
const HALF_LINE arc_extension_a{
slotArc.GetP0(),
slotArc.GetP0() + ( dogboneCenter - *corner ),
};
const HALF_LINE arc_extension_b{
slotArc.GetP1(),
slotArc.GetP1() + ( dogboneCenter - *corner ),
};
const OPT_VECTOR2I ext_a_intersect = arc_extension_a.Intersect( aSegA );
const OPT_VECTOR2I ext_b_intersect = arc_extension_b.Intersect( aSegB );
if( !ext_a_intersect || !ext_b_intersect )
{
// The arc extensions don't intersect the original segments
return std::nullopt;
}
{
// See if we need to update the original segments
// or if the dogbone consumed them
std::optional<SEG> new_a, new_b;
if( aOtherPtA != *ext_a_intersect )
new_a = SEG{ aOtherPtA, *ext_a_intersect };
if( aOtherPtB != *ext_b_intersect )
new_b = SEG{ aOtherPtB, *ext_b_intersect };
return DOGBONE_RESULT{
slotArc,
new_a,
new_b,
small_arc_mouth,
};
}
}

View File

@ -45,6 +45,21 @@ const VECTOR2I& KIGEOM::GetOtherEnd( const SEG& aSeg, const VECTOR2I& aPoint )
}
OPT_VECTOR2I KIGEOM::GetSharedEndpoint( const SEG& aSegA, const SEG& aSegB )
{
if( aSegA.A == aSegB.A || aSegA.A == aSegB.B )
{
return aSegA.A;
}
else if( aSegA.B == aSegB.A || aSegA.B == aSegB.B )
{
return aSegA.B;
}
return std::nullopt;
}
std::array<SEG, 4> KIGEOM::BoxToSegs( const BOX2I& aBox )
{
const std::array<VECTOR2I, 4> corners = {

View File

@ -68,6 +68,7 @@ using namespace std::placeholders;
#include <dialogs/dialog_track_via_properties.h>
#include <dialogs/dialog_tablecell_properties.h>
#include <dialogs/dialog_table_properties.h>
#include <dialogs/dialog_multi_unit_entry.h>
#include <dialogs/dialog_unit_entry.h>
#include <pcb_reference_image.h>
@ -1206,6 +1207,50 @@ static std::optional<int> GetRadiusParams( PCB_BASE_EDIT_FRAME& aFrame, const wx
}
static std::optional<DOGBONE_CORNER_ROUTINE::PARAMETERS>
GetDogboneParams( PCB_BASE_EDIT_FRAME& aFrame )
{
// Persistent parameters
static DOGBONE_CORNER_ROUTINE::PARAMETERS s_dogBoneParams{
pcbIUScale.mmToIU( 1 ),
true,
};
std::vector<WX_MULTI_ENTRY_DIALOG::ENTRY> entries{
{
_( "Arc radius:" ),
WX_MULTI_ENTRY_DIALOG::UNIT_BOUND{ s_dogBoneParams.DogboneRadiusIU },
wxEmptyString,
},
{
_( "Add slots in acute corners" ),
WX_MULTI_ENTRY_DIALOG::CHECKBOX{ s_dogBoneParams.AddSlots },
_( "Add slots in acute corners to allow access to a cutter of the given radius" ),
},
};
WX_MULTI_ENTRY_DIALOG dlg( &aFrame, _( "Dogbone Corner Settings" ), entries );
if( dlg.ShowModal() == wxID_CANCEL )
return std::nullopt;
std::vector<WX_MULTI_ENTRY_DIALOG::RESULT> results = dlg.GetValues();
wxCHECK( results.size() == 2, std::nullopt );
try
{
s_dogBoneParams.DogboneRadiusIU = std::get<long long int>( results[0] );
s_dogBoneParams.AddSlots = std::get<bool>( results[1] );
}
catch( const std::bad_variant_access& )
{
wxASSERT( false );
return std::nullopt;
}
return s_dogBoneParams;
}
/**
* Prompt the user for chamfer parameters
*
@ -1413,14 +1458,13 @@ int EDIT_TOOL::ModifyLines( const TOOL_EVENT& aEvent )
}
else if( aEvent.IsAction( &PCB_ACTIONS::dogboneCorners ) )
{
static int s_dogBoneRadius = pcbIUScale.mmToIU( 1 );
std::optional<int> radiusIU =
GetRadiusParams( *frame(), _( "Dogbone Corners" ), s_dogBoneRadius );
std::optional<DOGBONE_CORNER_ROUTINE::PARAMETERS> dogboneParams =
GetDogboneParams( *frame() );
if( radiusIU.has_value() )
if( dogboneParams.has_value() )
{
pairwise_line_routine = std::make_unique<DOGBONE_CORNER_ROUTINE>(
frame()->GetModel(), change_handler, *radiusIU );
frame()->GetModel(), change_handler, *dogboneParams );
}
}
else if( aEvent.IsAction( &PCB_ACTIONS::chamferLines ) )

View File

@ -285,6 +285,11 @@ std::optional<wxString> DOGBONE_CORNER_ROUTINE::GetStatusMessage() const
msg += _( "Some of the dogbone corners are too narrow to fit a "
"cutter of the specified radius." );
if( !m_params.AddSlots )
msg += _( " Consider enabling the 'Add Slots' option." );
else
msg += _( " Slots were added." );
}
if( msg.empty() )
@ -317,7 +322,7 @@ void DOGBONE_CORNER_ROUTINE::ProcessLinePair( PCB_SHAPE& aLineA, PCB_SHAPE& aLin
}
std::optional<DOGBONE_RESULT> dogbone_result =
ComputeDogbone( seg_a, seg_b, m_dogboneRadiusIU );
ComputeDogbone( seg_a, seg_b, m_params.DogboneRadiusIU, m_params.AddSlots );
if( !dogbone_result )
{
@ -331,17 +336,39 @@ void DOGBONE_CORNER_ROUTINE::ProcessLinePair( PCB_SHAPE& aLineA, PCB_SHAPE& aLin
m_haveNarrowMouths = true;
}
CHANGE_HANDLER& handler = GetHandler();
auto tArc = std::make_unique<PCB_SHAPE>( GetBoard(), SHAPE_T::ARC );
const auto copyProps = [&]( PCB_SHAPE& aShape )
{
aShape.SetWidth( aLineA.GetWidth() );
aShape.SetLayer( aLineA.GetLayer() );
aShape.SetLocked( aLineA.IsLocked() );
};
const auto addSegment = [&]( const SEG& aSeg )
{
if( aSeg.Length() == 0 )
return;
auto tSegment = std::make_unique<PCB_SHAPE>( GetBoard(), SHAPE_T::SEGMENT );
tSegment->SetStart( aSeg.A );
tSegment->SetEnd( aSeg.B );
copyProps( *tSegment );
handler.AddNewItem( std::move( tSegment ) );
};
tArc->SetArcGeometry( dogbone_result->m_arc.GetP0(), dogbone_result->m_arc.GetArcMid(),
dogbone_result->m_arc.GetP1() );
// Copy properties from one of the source lines
tArc->SetWidth( aLineA.GetWidth() );
tArc->SetLayer( aLineA.GetLayer() );
tArc->SetLocked( aLineA.IsLocked() );
copyProps( *tArc );
addSegment( SEG{ dogbone_result->m_arc.GetP0(), dogbone_result->m_updated_seg_a->B } );
addSegment( SEG{ dogbone_result->m_arc.GetP1(), dogbone_result->m_updated_seg_b->B } );
CHANGE_HANDLER& handler = GetHandler();
handler.AddNewItem( std::move( tArc ) );
ModifyLineOrDeleteIfZeroLength( aLineA, dogbone_result->m_updated_seg_a );

View File

@ -307,8 +307,14 @@ public:
class DOGBONE_CORNER_ROUTINE : public PAIRWISE_LINE_ROUTINE
{
public:
DOGBONE_CORNER_ROUTINE( BOARD_ITEM* aBoard, CHANGE_HANDLER& aHandler, int aDogboneRadiusIU ) :
PAIRWISE_LINE_ROUTINE( aBoard, aHandler ), m_dogboneRadiusIU( aDogboneRadiusIU ),
struct PARAMETERS
{
int DogboneRadiusIU;
bool AddSlots;
};
DOGBONE_CORNER_ROUTINE( BOARD_ITEM* aBoard, CHANGE_HANDLER& aHandler, PARAMETERS aParams ) :
PAIRWISE_LINE_ROUTINE( aBoard, aHandler ), m_params( std::move( aParams ) ),
m_haveNarrowMouths( false )
{
}
@ -319,7 +325,7 @@ public:
void ProcessLinePair( PCB_SHAPE& aLineA, PCB_SHAPE& aLineB ) override;
private:
int m_dogboneRadiusIU;
PARAMETERS m_params;
bool m_haveNarrowMouths;
};