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:
parent
17143e252f
commit
95beedb612
common
include/dialogs
libs/kimath
include/geometry
src/geometry
pcbnew/tools
@ -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
|
||||
|
176
common/dialogs/dialog_multi_unit_entry.cpp
Normal file
176
common/dialogs/dialog_multi_unit_entry.cpp
Normal 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;
|
||||
}
|
96
include/dialogs/dialog_multi_unit_entry.h
Normal file
96
include/dialogs/dialog_multi_unit_entry.h
Normal 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;
|
||||
};
|
@ -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_
|
||||
|
@ -85,4 +85,4 @@ struct DOGBONE_RESULT
|
||||
* | |
|
||||
*/
|
||||
std::optional<DOGBONE_RESULT> ComputeDogbone( const SEG& aSegA, const SEG& aSegB,
|
||||
int aDogboneRadius );
|
||||
int aDogboneRadius, bool aAddSlots );
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
@ -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 = {
|
||||
|
@ -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 ) )
|
||||
|
@ -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 );
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user