diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 7952f6ed2d..c44f6a0fcc 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -542,6 +542,7 @@ set( COMMON_DRAWING_SHEET_SRCS set( COMMON_PREVIEW_ITEMS_SRCS preview_items/anchor_debug.cpp + preview_items/angle_item.cpp preview_items/arc_assistant.cpp preview_items/arc_geom_manager.cpp preview_items/bezier_assistant.cpp diff --git a/common/preview_items/angle_item.cpp b/common/preview_items/angle_item.cpp new file mode 100644 index 0000000000..0bae7317b1 --- /dev/null +++ b/common/preview_items/angle_item.cpp @@ -0,0 +1,208 @@ +/* + * This program source code file is part of KICAD, a free EDA CAD application. + * + * Copyright (C) 2024 The 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace KIGFX::PREVIEW; + +ANGLE_ITEM::ANGLE_ITEM( EDIT_POINTS* aPoints ) : + SIMPLE_OVERLAY_ITEM(), + m_points( aPoints ) +{ +} + +const BOX2I ANGLE_ITEM::ViewBBox() const +{ + if( m_points ) + return m_points->ViewBBox(); + else + return BOX2I(); +} + +void ANGLE_ITEM::drawPreviewShape( KIGFX::VIEW* aView ) const +{ + if( !m_points ) + return; + + KIGFX::GAL* gal = aView->GetGAL(); + KIGFX::RENDER_SETTINGS* settings = aView->GetPainter()->GetSettings(); + KIGFX::COLOR4D drawColor = settings->GetLayerColor( LAYER_AUX_ITEMS ); + + double size = aView->ToWorld( EDIT_POINT::POINT_SIZE ) / 2.0; + double borderSize = aView->ToWorld( EDIT_POINT::BORDER_SIZE ); + double radius = size * 10.0; + + gal->SetStrokeColor( drawColor ); + gal->SetFillColor( drawColor ); + gal->SetIsFill( false ); + gal->SetLineWidth( borderSize * 2.0 ); + gal->SetGlyphSize( VECTOR2I( radius / 2, radius / 2 ) ); + + std::vector anglePoints; + + for( unsigned i = 0; i < m_points->PointsSize(); ++i ) + { + const EDIT_POINT& point = m_points->Point( i ); + + if( point.IsActive() || point.IsHover() ) + { + anglePoints.push_back( &point ); + + EDIT_POINT* prev = m_points->Previous( point ); + EDIT_POINT* next = m_points->Next( point ); + + if( prev ) + anglePoints.push_back( prev ); + + if( next ) + anglePoints.push_back( next ); + } + } + + std::sort( anglePoints.begin(), anglePoints.end() ); + anglePoints.erase( std::unique( anglePoints.begin(), anglePoints.end() ), anglePoints.end() ); + + // First pass: collect all angles and identify congruent ones + struct AngleInfo + { + const EDIT_POINT* point; + EDA_ANGLE angle; + VECTOR2D center; + VECTOR2D v1, v2; + EDA_ANGLE start; + EDA_ANGLE sweep; + EDA_ANGLE mid; + bool isRightAngle; + }; + + std::vector angles; + std::map angleCount; // Map angle (in tenths of degree) to count + + for( const EDIT_POINT* pt : anglePoints ) + { + EDIT_POINT* prev = m_points->Previous( *pt ); + EDIT_POINT* next = m_points->Next( *pt ); + + if( !( prev && next ) ) + continue; + + SEG seg1( pt->GetPosition(), prev->GetPosition() ); + SEG seg2( pt->GetPosition(), next->GetPosition() ); + + // Calculate the interior angle (0-180 degrees) instead of the smallest angle + VECTOR2I c = pt->GetPosition(); + VECTOR2I v1 = prev->GetPosition() - c; + VECTOR2I v2 = next->GetPosition() - c; + EDA_ANGLE angle = seg2.Angle( seg1 ); + + EDA_ANGLE start = EDA_ANGLE( VECTOR2D( v1 ) ); + EDA_ANGLE sweep = ( EDA_ANGLE( VECTOR2D( v2 ) ) - start ).Normalize180(); + EDA_ANGLE mid = start + sweep / 2.0; + VECTOR2D center( c ); + + AngleInfo info; + info.point = pt; + info.angle = angle; + info.center = center; + info.v1 = v1; + info.v2 = v2; + info.start = start; + info.sweep = sweep; + info.mid = mid; + info.isRightAngle = ( angle.AsTenthsOfADegree() == 900 ); + + angles.push_back( info ); + angleCount[angle.AsTenthsOfADegree()]++; + } + + // Second pass: draw the angles with congruence markings + for( const AngleInfo& angleInfo : angles ) + { + bool isCongruent = angleCount[angleInfo.angle.AsTenthsOfADegree()] > 1; + + if( angleInfo.isRightAngle ) + { + VECTOR2D u1 = VECTOR2D( angleInfo.v1 ).Resize( radius ); + VECTOR2D u2 = VECTOR2D( angleInfo.v2 ).Resize( radius ); + VECTOR2D p1 = angleInfo.center + u1; + VECTOR2D p2 = angleInfo.center + u2; + VECTOR2D corner = angleInfo.center + u1 + u2; + + // Draw the primary right angle marker + gal->DrawLine( p1, corner ); + gal->DrawLine( p2, corner ); + + // Draw congruence marking for right angles + if( isCongruent ) + { + double innerRadius = radius * 0.6; + VECTOR2D u1_inner = VECTOR2D( angleInfo.v1 ).Resize( innerRadius ); + VECTOR2D u2_inner = VECTOR2D( angleInfo.v2 ).Resize( innerRadius ); + VECTOR2D p1_inner = angleInfo.center + u1_inner; + VECTOR2D p2_inner = angleInfo.center + u2_inner; + VECTOR2D corner_inner = angleInfo.center + u1_inner + u2_inner; + + gal->DrawLine( p1_inner, corner_inner ); + gal->DrawLine( p2_inner, corner_inner ); + } + } + else + { + // Draw the primary arc + gal->DrawArc( angleInfo.center, radius, angleInfo.start, angleInfo.sweep ); + + // Draw congruence marking for non-right angles + if( isCongruent ) + { + double innerRadius = radius * 0.7; + gal->DrawArc( angleInfo.center, innerRadius, angleInfo.start, angleInfo.sweep ); + } + } + + VECTOR2D textDir( angleInfo.mid.Cos(), angleInfo.mid.Sin() ); + wxString label = wxString::Format( wxT( "%.1f°" ), angleInfo.angle.AsDegrees() ); + + // Calculate actual text dimensions to ensure proper clearance + KIFONT::FONT* font = KIFONT::FONT::GetFont(); + VECTOR2I textSize = font->StringBoundaryLimits( label, gal->GetGlyphSize(), 0, false, false, + KIFONT::METRICS::Default() ); + + // Calculate offset based on text direction - use width for horizontal, height for vertical + double absX = std::abs( textDir.x ); + double absY = std::abs( textDir.y ); + double textClearance = ( absX * textSize.x + absY * textSize.y ) / 2.0; + double textOffset = radius + borderSize + textClearance; + VECTOR2I textPos = angleInfo.center + VECTOR2I( textDir * textOffset ); + gal->BitmapText( label, textPos, ANGLE_HORIZONTAL ); + } +} diff --git a/common/tool/edit_points.cpp b/common/tool/edit_points.cpp index 00ad032206..b9ef9dbe7b 100644 --- a/common/tool/edit_points.cpp +++ b/common/tool/edit_points.cpp @@ -27,6 +27,10 @@ #include #include #include // for KiROUND +#include +#include +#include +#include #include "tool/edit_points.h" @@ -331,4 +335,5 @@ void EDIT_POINTS::ViewDraw( int aLayer, KIGFX::VIEW* aView ) const gal->DrawLine( line.GetOrigin().GetPosition(), line.GetEnd().GetPosition() ); } } + } diff --git a/eeschema/tools/sch_point_editor.cpp b/eeschema/tools/sch_point_editor.cpp index 698877f22a..bae5f898cf 100644 --- a/eeschema/tools/sch_point_editor.cpp +++ b/eeschema/tools/sch_point_editor.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -1068,7 +1069,9 @@ int SCH_POINT_EDITOR::Main( const TOOL_EVENT& aEvent ) controls->ShowCursor( true ); makePointsAndBehavior( item ); + m_angleItem = std::make_unique( m_editPoints.get() ); view->Add( m_editPoints.get() ); + view->Add( m_angleItem.get() ); setEditedPoint( nullptr ); updateEditedPoint( aEvent ); bool inDrag = false; @@ -1188,8 +1191,10 @@ int SCH_POINT_EDITOR::Main( const TOOL_EVENT& aEvent ) if( m_editPoints ) { view->Remove( m_editPoints.get() ); + view->Remove( m_angleItem.get() ); m_editPoints.reset(); + m_angleItem.reset(); m_frame->GetCanvas()->Refresh(); } @@ -1226,6 +1231,7 @@ void SCH_POINT_EDITOR::updatePoints() m_editBehavior->UpdatePoints( *m_editPoints ); getView()->Update( m_editPoints.get() ); + getView()->Update( m_angleItem.get() ); } diff --git a/eeschema/tools/sch_point_editor.h b/eeschema/tools/sch_point_editor.h index 187e10a00d..936cecb15d 100644 --- a/eeschema/tools/sch_point_editor.h +++ b/eeschema/tools/sch_point_editor.h @@ -29,6 +29,8 @@ #include #include +namespace KIGFX { namespace PREVIEW { class ANGLE_ITEM; } } + class SCH_SELECTION_TOOL; class POINT_EDIT_BEHAVIOR; class SCH_BASE_FRAME; @@ -121,6 +123,7 @@ private: ///< Currently available edit points. std::shared_ptr m_editPoints; + std::unique_ptr m_angleItem; ///< Current item-specific edit behavior. std::unique_ptr m_editBehavior; diff --git a/include/preview_items/angle_item.h b/include/preview_items/angle_item.h new file mode 100644 index 0000000000..5559faabc2 --- /dev/null +++ b/include/preview_items/angle_item.h @@ -0,0 +1,57 @@ +/* + * This program source code file is part of KICAD, a free EDA CAD application. + * + * Copyright (C) 2024 The 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 + */ + +#ifndef PREVIEW_ANGLE_ITEM_H +#define PREVIEW_ANGLE_ITEM_H + +#include + +class EDIT_POINTS; + +namespace KIGFX +{ +namespace PREVIEW +{ + +class ANGLE_ITEM : public SIMPLE_OVERLAY_ITEM +{ +public: + ANGLE_ITEM( EDIT_POINTS* aPoints ); + + const BOX2I ViewBBox() const override; + + void SetEditPoints( EDIT_POINTS* aPoints ) + { + m_points = aPoints; + } + +private: + void drawPreviewShape( KIGFX::VIEW* aView ) const override; + + EDIT_POINTS* m_points; +}; + +} // PREVIEW +} // KIGFX + +#endif diff --git a/pagelayout_editor/tools/pl_point_editor.cpp b/pagelayout_editor/tools/pl_point_editor.cpp index 8e81dff58b..0ec9451959 100644 --- a/pagelayout_editor/tools/pl_point_editor.cpp +++ b/pagelayout_editor/tools/pl_point_editor.cpp @@ -32,6 +32,7 @@ using namespace std::placeholders; #include #include #include +#include #include #include #include @@ -174,7 +175,10 @@ int PL_POINT_EDITOR::Main( const TOOL_EVENT& aEvent ) if( !m_editPoints ) return 0; + m_angleItem = std::make_unique( m_editPoints.get() ); + getView()->Add( m_editPoints.get() ); + getView()->Add( m_angleItem.get() ); setEditedPoint( nullptr ); updateEditedPoint( aEvent ); bool inDrag = false; @@ -252,11 +256,13 @@ int PL_POINT_EDITOR::Main( const TOOL_EVENT& aEvent ) if( m_editPoints ) { getView()->Remove( m_editPoints.get() ); + getView()->Remove( m_angleItem.get() ); if( modified ) m_frame->OnModify(); m_editPoints.reset(); + m_angleItem.reset(); m_frame->GetCanvas()->Refresh(); } @@ -462,6 +468,7 @@ void PL_POINT_EDITOR::updatePoints() } getView()->Update( m_editPoints.get() ); + getView()->Update( m_angleItem.get() ); } diff --git a/pagelayout_editor/tools/pl_point_editor.h b/pagelayout_editor/tools/pl_point_editor.h index a35a25665c..8a13d54c0c 100644 --- a/pagelayout_editor/tools/pl_point_editor.h +++ b/pagelayout_editor/tools/pl_point_editor.h @@ -30,6 +30,8 @@ #include #include +namespace KIGFX { namespace PREVIEW { class ANGLE_ITEM; } } + class PL_SELECTION_TOOL; class PL_EDITOR_FRAME; @@ -99,6 +101,7 @@ private: ///< Currently available edit points. std::shared_ptr m_editPoints; + std::unique_ptr m_angleItem; }; #endif // PL_POINT_EDITOR_H diff --git a/pcbnew/tools/pcb_point_editor.cpp b/pcbnew/tools/pcb_point_editor.cpp index 35e8df7bd3..2c871779f5 100644 --- a/pcbnew/tools/pcb_point_editor.cpp +++ b/pcbnew/tools/pcb_point_editor.cpp @@ -41,6 +41,7 @@ using namespace std::placeholders; #include #include #include +#include #include #include #include @@ -2011,6 +2012,8 @@ int PCB_POINT_EDITOR::OnSelectionChange( const TOOL_EVENT& aEvent ) if( !m_editPoints ) return 0; + m_angleItem = std::make_unique( m_editPoints.get() ); + m_preview.FreeItems(); getView()->Add( &m_preview ); @@ -2019,6 +2022,7 @@ int PCB_POINT_EDITOR::OnSelectionChange( const TOOL_EVENT& aEvent ) m_preview.Add( radiusHelper ); getView()->Add( m_editPoints.get() ); + getView()->Add( m_angleItem.get() ); setEditedPoint( nullptr ); updateEditedPoint( aEvent ); bool inDrag = false; @@ -2047,7 +2051,10 @@ int PCB_POINT_EDITOR::OnSelectionChange( const TOOL_EVENT& aEvent ) updateEditedPoint( *evt ); if( prevHover != m_hoveredPoint ) + { getView()->Update( m_editPoints.get() ); + getView()->Update( m_angleItem.get() ); + } if( evt->IsDrag( BUT_LEFT ) && m_editedPoint ) { @@ -2191,6 +2198,7 @@ int PCB_POINT_EDITOR::OnSelectionChange( const TOOL_EVENT& aEvent ) } getView()->Update( m_editPoints.get() ); + getView()->Update( m_angleItem.get() ); } else if( inDrag && evt->IsMouseUp( BUT_LEFT ) ) { @@ -2198,6 +2206,7 @@ int PCB_POINT_EDITOR::OnSelectionChange( const TOOL_EVENT& aEvent ) { m_editedPoint->SetActive( false ); getView()->Update( m_editPoints.get() ); + getView()->Update( m_angleItem.get() ); } radiusHelper->Hide(); @@ -2269,8 +2278,11 @@ int PCB_POINT_EDITOR::OnSelectionChange( const TOOL_EVENT& aEvent ) if( item->Type() == PCB_PAD_T && m_isFootprintEditor ) { getView()->Remove( m_editPoints.get() ); + getView()->Remove( m_angleItem.get() ); m_editPoints = makePoints( item ); + m_angleItem->SetEditPoints( m_editPoints.get() ); getView()->Add( m_editPoints.get() ); + getView()->Add( m_angleItem.get() ); } } else if( evt->Action() == TA_UNDO_REDO_POST ) @@ -2295,7 +2307,9 @@ int PCB_POINT_EDITOR::OnSelectionChange( const TOOL_EVENT& aEvent ) if( m_editPoints ) { getView()->Remove( m_editPoints.get() ); + getView()->Remove( m_angleItem.get() ); m_editPoints.reset(); + m_angleItem.reset(); } m_editedPoint = nullptr; @@ -2431,6 +2445,7 @@ void PCB_POINT_EDITOR::updatePoints() m_editorBehavior->UpdatePoints( *m_editPoints ); getView()->Update( m_editPoints.get() ); + getView()->Update( m_angleItem.get() ); } diff --git a/pcbnew/tools/pcb_point_editor.h b/pcbnew/tools/pcb_point_editor.h index 5dda002d18..6d3af7103a 100644 --- a/pcbnew/tools/pcb_point_editor.h +++ b/pcbnew/tools/pcb_point_editor.h @@ -34,6 +34,8 @@ #include +namespace KIGFX { namespace PREVIEW { class ANGLE_ITEM; } } + class PCB_SELECTION_TOOL; class POINT_EDIT_BEHAVIOR; @@ -125,6 +127,7 @@ private: PCB_BASE_FRAME* m_frame; PCB_SELECTION_TOOL* m_selectionTool; std::shared_ptr m_editPoints; + std::unique_ptr m_angleItem; EDIT_POINT* m_editedPoint; EDIT_POINT* m_hoveredPoint;