diff --git a/3d-viewer/3d_canvas.cpp b/3d-viewer/3d_canvas.cpp index 248cd3bb3d..1764d6faa0 100644 --- a/3d-viewer/3d_canvas.cpp +++ b/3d-viewer/3d_canvas.cpp @@ -228,8 +228,6 @@ void EDA_3D_CANVAS::SetView3D( int keycode ) void EDA_3D_CANVAS::OnMouseWheel( wxMouseEvent& event ) { - wxSize size( GetClientSize() ); - if( event.ShiftDown() ) { if( event.GetWheelRotation() < 0 ) diff --git a/common/drawpanel.cpp b/common/drawpanel.cpp index 030f1328c9..a055fb83b8 100644 --- a/common/drawpanel.cpp +++ b/common/drawpanel.cpp @@ -906,11 +906,22 @@ void EDA_DRAW_PANEL::OnMouseWheel( wxMouseEvent& event ) bool offCenterReq = event.ControlDown() && event.ShiftDown(); offCenterReq = offCenterReq || m_enableZoomNoCenter; +#if wxMAJOR_VERSION >= 2 && wxMINOR_VERSION >= 9 + int axis = event.GetWheelAxis(); +#else + const int axis = 0; +#endif + // This is a zoom in or out command if( event.GetWheelRotation() > 0 ) { if( event.ShiftDown() && !event.ControlDown() ) - cmd.SetId( ID_PAN_UP ); + { + if( axis == 0 ) + cmd.SetId( ID_PAN_UP ); + else + cmd.SetId( ID_PAN_RIGHT ); + } else if( event.ControlDown() && !event.ShiftDown() ) cmd.SetId( ID_PAN_LEFT ); else if( offCenterReq ) @@ -921,7 +932,12 @@ void EDA_DRAW_PANEL::OnMouseWheel( wxMouseEvent& event ) else if( event.GetWheelRotation() < 0 ) { if( event.ShiftDown() && !event.ControlDown() ) - cmd.SetId( ID_PAN_DOWN ); + { + if( axis == 0 ) + cmd.SetId( ID_PAN_DOWN ); + else + cmd.SetId( ID_PAN_LEFT ); + } else if( event.ControlDown() && !event.ShiftDown() ) cmd.SetId( ID_PAN_RIGHT ); else if( offCenterReq ) diff --git a/include/wxPcbStruct.h b/include/wxPcbStruct.h index df4420d61f..fc7d379a07 100644 --- a/include/wxPcbStruct.h +++ b/include/wxPcbStruct.h @@ -1494,7 +1494,7 @@ public: // Autoplacement: - void AutoPlace( wxCommandEvent& event ); + void OnPlaceOrRouteFootprints( wxCommandEvent& event ); /** * Function ScriptingConsoleEnableDisable @@ -1520,7 +1520,17 @@ public: */ bool ReOrientModules( const wxString& ModuleMask, double Orient, bool include_fixe ); void LockModule( MODULE* aModule, bool aLocked ); - void AutoMoveModulesOnPcb( bool PlaceModulesHorsPcb ); + + /** + * Function SpreadFootprints + * Footprints (after loaded by reading a netlist for instance) are moved + * to be in a small free area (outside the current board) without overlapping. + * @param aFootprintsOutsideBoardOnly: true to move only + * footprints outside the board outlines + * (they are outside if the position of a footprint is outside + * the board outlines bounding box + */ + void SpreadFootprints( bool aFootprintsOutsideBoardOnly ); /** * Function AutoPlaceModule diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt index f243ce065f..360f98a5c3 100644 --- a/pcbnew/CMakeLists.txt +++ b/pcbnew/CMakeLists.txt @@ -124,12 +124,14 @@ set( PCBNEW_IMPORT_DXF ) set( PCBNEW_AUTOROUTER_SRCS - autorouter/automove.cpp - autorouter/autoplac.cpp + autorouter/rect_placement/rect_placement.cpp + autorouter/move_and_route_event_functions.cpp + autorouter/auto_place_footprints.cpp autorouter/autorout.cpp autorouter/routing_matrix.cpp autorouter/dist.cpp autorouter/queue.cpp + autorouter/spread_footprints.cpp autorouter/solve.cpp autorouter/graphpcb.cpp autorouter/work.cpp diff --git a/pcbnew/autorouter/autoplac.cpp b/pcbnew/autorouter/auto_place_footprints.cpp similarity index 99% rename from pcbnew/autorouter/autoplac.cpp rename to pcbnew/autorouter/auto_place_footprints.cpp index fb604103e0..7322f5690b 100644 --- a/pcbnew/autorouter/autoplac.cpp +++ b/pcbnew/autorouter/auto_place_footprints.cpp @@ -1085,7 +1085,7 @@ static bool Tri_PlaceModules( MODULE* ref, MODULE* compare ) } -static bool Tri_RatsModules( MODULE* ref, MODULE* compare ) +static bool sortFootprintsByRatsnestSize( MODULE* ref, MODULE* compare ) { double ff1, ff2; @@ -1141,7 +1141,7 @@ static MODULE* PickModule( PCB_EDIT_FRAME* pcbframe, wxDC* DC ) pcbframe->GetBoard()->m_Status_Pcb &= ~RATSNEST_ITEM_LOCAL_OK; - sort( moduleList.begin(), moduleList.end(), Tri_RatsModules ); + sort( moduleList.begin(), moduleList.end(), sortFootprintsByRatsnestSize ); // Search for "best" module. MODULE* bestModule = NULL; diff --git a/pcbnew/autorouter/automove.cpp b/pcbnew/autorouter/move_and_route_event_functions.cpp similarity index 55% rename from pcbnew/autorouter/automove.cpp rename to pcbnew/autorouter/move_and_route_event_functions.cpp index 19d04e5885..8fb6ffcf56 100644 --- a/pcbnew/autorouter/automove.cpp +++ b/pcbnew/autorouter/move_and_route_event_functions.cpp @@ -55,15 +55,13 @@ typedef enum { } SelectFixeFct; -static bool sortModulesbySize( MODULE* ref, MODULE* compare ); - wxString ModulesMaskSelection = wxT( "*" ); /* Called on events (popup menus) relative to automove and autoplace footprints */ -void PCB_EDIT_FRAME::AutoPlace( wxCommandEvent& event ) +void PCB_EDIT_FRAME::OnPlaceOrRouteFootprints( wxCommandEvent& event ) { int id = event.GetId(); @@ -130,12 +128,19 @@ void PCB_EDIT_FRAME::AutoPlace( wxCommandEvent& event ) AutoPlaceModule( NULL, PLACE_INCREMENTAL, &dc ); break; - case ID_POPUP_PCB_AUTOMOVE_ALL_MODULES: - AutoMoveModulesOnPcb( false ); - break; + case ID_POPUP_PCB_SPREAD_ALL_MODULES: + if( !IsOK( this, + _("Not locked footprints inside the board will be moved. OK?") ) ) + break; + // Fall through + case ID_POPUP_PCB_SPREAD_NEW_MODULES: + if( GetBoard()->m_Modules == NULL ) + { + DisplayError( this, _( "No modules found!" ) ); + return; + } - case ID_POPUP_PCB_AUTOMOVE_NEW_MODULES: - AutoMoveModulesOnPcb( true ); + SpreadFootprints( id == ID_POPUP_PCB_SPREAD_NEW_MODULES ); break; case ID_POPUP_PCB_AUTOROUTE_ALL_MODULES: @@ -159,7 +164,7 @@ void PCB_EDIT_FRAME::AutoPlace( wxCommandEvent& event ) break; default: - wxMessageBox( wxT( "AutoPlace command error" ) ); + wxMessageBox( wxT( "OnPlaceOrRouteFootprints command error" ) ); break; } @@ -168,135 +173,6 @@ void PCB_EDIT_FRAME::AutoPlace( wxCommandEvent& event ) } -/* Function to move components in a rectangular area format 4 / 3, - * starting from the mouse cursor - * The components with the FIXED status set are not moved - */ -void PCB_EDIT_FRAME::AutoMoveModulesOnPcb( bool PlaceModulesHorsPcb ) -{ - std::vector moduleList; - wxPoint start, current; - int Ymax_size, Xsize_allowed; - int pas_grille = (int) GetScreen()->GetGridSize().x; - double surface; - - // Undo: init list - PICKED_ITEMS_LIST newList; - newList.m_Status = UR_CHANGED; - ITEM_PICKER picker( NULL, UR_CHANGED ); - - if( GetBoard()->m_Modules == NULL ) - { - DisplayError( this, _( "No modules found!" ) ); - return; - } - - // Confirmation - if( !IsOK( this, _( "Move modules?" ) ) ) - return; - - EDA_RECT bbbox = GetBoard()->ComputeBoundingBox( true ); - - bool edgesExist = ( bbbox.GetWidth() || bbbox.GetHeight() ); - - // no edges exist - if( PlaceModulesHorsPcb && !edgesExist ) - { - DisplayError( this, - _( "Could not automatically place modules. No board outlines detected." ) ); - return; - } - - // Build sorted footprints list (sort by decreasing size ) - MODULE* Module = GetBoard()->m_Modules; - - for( ; Module != NULL; Module = Module->Next() ) - { - Module->CalculateBoundingBox(); - moduleList.push_back(Module); - } - - sort( moduleList.begin(), moduleList.end(), sortModulesbySize ); - - /* to move modules outside the board, the cursor is placed below - * the current board, to avoid placing components in board area. - */ - if( PlaceModulesHorsPcb && edgesExist ) - { - if( GetCrossHairPosition().y < (bbbox.GetBottom() + 2000) ) - { - wxPoint pos = GetCrossHairPosition(); - pos.y = bbbox.GetBottom() + 2000; - SetCrossHairPosition( pos ); - } - } - - // calculate the area needed by footprints - surface = 0.0; - - for( unsigned ii = 0; ii < moduleList.size(); ii++ ) - { - Module = moduleList[ii]; - - if( PlaceModulesHorsPcb && edgesExist ) - { - if( bbbox.Contains( Module->GetPosition() ) ) - continue; - } - - surface += Module->GetArea(); - } - - Xsize_allowed = (int) ( sqrt( surface ) * 4.0 / 3.0 ); - - start = current = GetCrossHairPosition(); - Ymax_size = 0; - - for( unsigned ii = 0; ii < moduleList.size(); ii++ ) - { - Module = moduleList[ii]; - - if( Module->IsLocked() ) - continue; - - if( PlaceModulesHorsPcb && edgesExist ) - { - if( bbbox.Contains( Module->GetPosition() ) ) - continue; - } - - // Undo: add copy of old Module to undo - picker.SetItem( Module ); - picker.SetLink( Module->Clone() ); - - if( current.x > (Xsize_allowed + start.x) ) - { - current.x = start.x; - current.y += Ymax_size + pas_grille; - Ymax_size = 0; - } - - SetCrossHairPosition( current + Module->GetPosition() - - Module->GetBoundingBox().GetPosition() ); - - Ymax_size = std::max( Ymax_size, Module->GetBoundingBox().GetHeight() ); - - PlaceModule( Module, NULL, true ); - - // Undo: add new Module to undo - newList.PushItem( picker ); - - current.x += Module->GetBoundingBox().GetWidth() + pas_grille; - } - - // Undo: commit - if( newList.GetCount() ) - SaveCopyInUndoList( newList, UR_CHANGED ); - - m_canvas->Refresh(); -} - - /* Set or reset (true or false) Lock attribute of aModule or all modules if aModule == NULL */ void PCB_EDIT_FRAME::LockModule( MODULE* aModule, bool aLocked ) @@ -322,8 +198,3 @@ void PCB_EDIT_FRAME::LockModule( MODULE* aModule, bool aLocked ) } } - -static bool sortModulesbySize( MODULE* ref, MODULE* compare ) -{ - return compare->GetArea() < ref->GetArea(); -} diff --git a/pcbnew/autorouter/rect_placement/RectanglePlacement.txt b/pcbnew/autorouter/rect_placement/RectanglePlacement.txt new file mode 100644 index 0000000000..cecbf2efd9 --- /dev/null +++ b/pcbnew/autorouter/rect_placement/RectanglePlacement.txt @@ -0,0 +1,38 @@ +A class that fits subrectangles into a power-of-2 rectangle + +(C) Copyright 2000-2002 by Javier Arevalo +This code is free to use and modify for all purposes + +You have a bunch of rectangular pieces. You need to arrange them in a +rectangular surface so that they don't overlap, keeping the total area of the +rectangle as small as possible. This is fairly common when arranging characters +in a bitmapped font, lightmaps for a 3D engine, and I guess other situations as +well. + +The idea of this algorithm is that, as we add rectangles, we can pre-select +"interesting" places where we can try to add the next rectangles. For optimal +results, the rectangles should be added in order. I initially tried using area +as a sorting criteria, but it didn't work well with very tall or very flat +rectangles. I then tried using the longest dimension as a selector, and it +worked much better. So much for intuition... + +These "interesting" places are just to the right and just below the currently +added rectangle. The first rectangle, obviously, goes at the top left, the next +one would go either to the right or below this one, and so on. It is a weird way +to do it, but it seems to work very nicely. + +The way we search here is fairly brute-force, the fact being that for most off- +line purposes the performance seems more than adequate. I have generated a +japanese font with around 8500 characters and all the time was spent generating +the bitmaps. + +Also, for all we care, we could grow the parent rectangle in a different way +than power of two. It just happens that power of 2 is very convenient for +graphics hardware textures. + +I'd be interested in hearing of other approaches to this problem. Make sure +to post them on http://www.flipcode.com + +See also +http://www.flipcode.com/archives/Rectangle_Placement.shtml +http://kossovsky.net/index.php/2009/07/cshar-rectangle-packing diff --git a/pcbnew/autorouter/rect_placement/rect_placement.cpp b/pcbnew/autorouter/rect_placement/rect_placement.cpp new file mode 100644 index 0000000000..f562c2b9ae --- /dev/null +++ b/pcbnew/autorouter/rect_placement/rect_placement.cpp @@ -0,0 +1,259 @@ +// ---------------------------------------------------------------------------------------- +// Name : rect_placement.cpp +// Description : A class that fits subrectangles into a power-of-2 rectangle +// (C) Copyright 2000-2002 by Javier Arevalo +// This code is free to use and modify for all purposes +// ---------------------------------------------------------------------------------------- + +/* + * You have a bunch of rectangular pieces. You need to arrange them in a + * rectangular surface so that they don't overlap, keeping the total area of the + * rectangle as small as possible. This is fairly common when arranging characters + * in a bitmapped font, lightmaps for a 3D engine, and I guess other situations as + * well. + * + * The idea of this algorithm is that, as we add rectangles, we can pre-select + * "interesting" places where we can try to add the next rectangles. For optimal + * results, the rectangles should be added in order. I initially tried using area + * as a sorting criteria, but it didn't work well with very tall or very flat + * rectangles. I then tried using the longest dimension as a selector, and it + * worked much better. So much for intuition... + * + * These "interesting" places are just to the right and just below the currently + * added rectangle. The first rectangle, obviously, goes at the top left, the next + * one would go either to the right or below this one, and so on. It is a weird way + * to do it, but it seems to work very nicely. + * + * The way we search here is fairly brute-force, the fact being that for most off- + * line purposes the performance seems more than adequate. I have generated a + * japanese font with around 8500 characters and all the time was spent generating + * the bitmaps. + * + * Also, for all we care, we could grow the parent rectangle. + * + * I'd be interested in hearing of other approaches to this problem. Make sure + * to post them on http://www.flipcode.com + */ + +#include "rect_placement.h" + +// -------------------------------------------------------------------------------- +// Name : +// Description : +// -------------------------------------------------------------------------------- +void CRectPlacement::Init( int w, int h ) +{ + End(); + m_size = TRect( 0, 0, w, h ); + m_vPositions.push_back( TPos( 0, 0 ) ); + m_area = 0; +} + + +// -------------------------------------------------------------------------------- +// Name : +// Description : +// -------------------------------------------------------------------------------- +void CRectPlacement::End() +{ + m_vPositions.clear(); + m_vRects.clear(); + m_size.w = 0; +} + + +// -------------------------------------------------------------------------------- +// Name : IsFree +// Description : Check if the given rectangle is partially or totally used +// -------------------------------------------------------------------------------- +bool CRectPlacement::IsFree( const TRect& r ) const +{ + if( !m_size.Contains( r ) ) + return false; + + for( CRectArray::const_iterator it = m_vRects.begin(); + it != m_vRects.end(); ++it ) + { + if( it->Intersects( r ) ) + return false; + } + + return true; +} + + +// -------------------------------------------------------------------------------- +// Name : AddPosition +// Description : Add new anchor point +// -------------------------------------------------------------------------------- +void CRectPlacement::AddPosition( const TPos& p ) +{ + // Try to insert anchor as close as possible to the top left corner + // So it will be tried first + bool bFound = false; + CPosArray::iterator it; + + for( it = m_vPositions.begin(); + !bFound && it != m_vPositions.end(); + ++it ) + { + if( p.x + p.y < it->x + it->y ) + bFound = true; + } + + if( bFound ) + m_vPositions.insert( it, p ); + else + m_vPositions.push_back( p ); +} + + +// -------------------------------------------------------------------------------- +// Name : AddRect +// Description : Add the given rect and updates anchor points +// -------------------------------------------------------------------------------- +void CRectPlacement::AddRect( const TRect& r ) +{ + m_vRects.push_back( r ); + m_area += r.w * r.h; + + // Add two new anchor points + AddPosition( TPos( r.x, r.y + r.h ) ); + AddPosition( TPos( r.x + r.w, r.y ) ); +} + + +// -------------------------------------------------------------------------------- +// Name : AddAtEmptySpot +// Description : Add the given rectangle +// -------------------------------------------------------------------------------- +bool CRectPlacement::AddAtEmptySpot( TRect& r ) +{ + // Find a valid spot among available anchors. + bool bFound = false; + CPosArray::iterator it; + + for( it = m_vPositions.begin(); + !bFound && it != m_vPositions.end(); + ++it ) + { + TRect Rect( it->x, it->y, r.w, r.h ); + + if( IsFree( Rect ) ) + { + r = Rect; + bFound = true; + break; // Don't let the loop increase the iterator. + } + } + + if( bFound ) + { + int x, y; + + // Remove the used anchor point + m_vPositions.erase( it ); + + // Sometimes, anchors end up displaced from the optimal position + // due to irregular sizes of the subrects. + // So, try to adjut it up & left as much as possible. + for( x = 1; x <= r.x; x++ ) + { + if( !IsFree( TRect( r.x - x, r.y, r.w, r.h ) ) ) + break; + } + + for( y = 1; y <= r.y; y++ ) + { + if( !IsFree( TRect( r.x, r.y - y, r.w, r.h ) ) ) + break; + } + + if( y > x ) + r.y -= y - 1; + else + r.x -= x - 1; + + AddRect( r ); + } + + return bFound; +} + +#include +// -------------------------------------------------------------------------------- +// Name : AddAtEmptySpotAutoGrow +// Description : Add a rectangle of the given size, growing our area if needed +// Area grows only until the max given. +// Returns the placement of the rect in the rect's x,y coords +// -------------------------------------------------------------------------------- +bool CRectPlacement::AddAtEmptySpotAutoGrow( TRect* pRect, int maxW, int maxH ) +{ + double growing_factor = 1.2; // Must be > 1.0, and event > 1.1 for fast optimization + + #define GROW(x) ((x * growing_factor) + 1) + + if( pRect->w <= 0 ) + return true; + + int orgW = m_size.w; + int orgH = m_size.h; + + // Try to add it in the existing space + while( !AddAtEmptySpot( *pRect ) ) + { + int pw = m_size.w; + int ph = m_size.h; + + // Sanity check - if area is complete. + if( pw >= maxW && ph >= maxH ) + { + m_size.w = orgW; + m_size.h = orgH; + return false; + } + + // Try growing the smallest dim + if( pw < maxW && ( pw < ph || ( (pw == ph) && (pRect->w >= pRect->h) ) ) ) + m_size.w = GROW( pw ); + else + m_size.h = GROW( ph ); + + if( AddAtEmptySpot( *pRect ) ) + break; + + // Try growing the other dim instead + if( pw != m_size.w ) + { + m_size.w = pw; + + if( ph < maxW ) + m_size.h = GROW( ph ); + } + else + { + m_size.h = ph; + + if( pw < maxW ) + m_size.w = GROW( pw ); + } + + if( pw != m_size.w || ph != m_size.h ) + if( AddAtEmptySpot( *pRect ) ) + break; + + + + // Grow both if possible, and reloop. + m_size.w = pw; + m_size.h = ph; + + if( pw < maxW ) + m_size.w = GROW( pw ); + + if( ph < maxH ) + m_size.h = GROW( ph ); + } + + return true; +} diff --git a/pcbnew/autorouter/rect_placement/rect_placement.h b/pcbnew/autorouter/rect_placement/rect_placement.h new file mode 100644 index 0000000000..c9d9585f5d --- /dev/null +++ b/pcbnew/autorouter/rect_placement/rect_placement.h @@ -0,0 +1,104 @@ +// -------------------------------------------------------------------------------- +// Name : rect_placement.h +// Description : A class that allocates subrectangles into power-of-2 rectangles +// (C) Copyright 2000-2002 by Javier Arevalo +// This code is free to use and modify for all purposes +// -------------------------------------------------------------------------------- + +/** + * @file rect_placement.h + */ + +#ifndef _RECT_PLACEMENT_H_ +#define _RECT_PLACEMENT_H_ + +#include + +// -------------------------------------------------------------------------------- +// -------------------------------------------------------------------------------- + +class CRectPlacement +{ +public: + + // Helper classes + struct TPos + { + int x, y; + + TPos() { } + TPos( int _x, int _y ) : x( _x ), y( _y ) { } + + bool operator ==( const TPos& p ) const { return x == p.x && y == p.y; } + }; + + struct TRect : public TPos + { + int w, h; + + TRect() { } + TRect( int _x, int _y, int _w, int _h ) : TPos( _x, _y ), w( _w > 0 ? _w : 0 ), h( + _h > 0 ? _h : 0 ) { } + + bool Contains( const TPos& p ) const + { + return p.x >= x && p.y >= y && p.x < (x + w) && p.y < (y + h); + } + bool Contains( const TRect& r ) const + { + return r.x >= x && r.y >= y && + (r.x + r.w) <= (x + w) && (r.y + r.h) <= (y + h); + } + bool Intersects( const TRect& r ) const + { + return w > 0 && h > 0 && r.w > 0 && r.h > 0 + && ( (r.x + r.w) > x && r.x < (x + w) && (r.y + r.h) > y && r.y < (y + h) ); + } + + // static bool Greater(const TRect &a, const TRect &b) + // { return a.w*a.h > b.w*b.h; } + // Greater rect area. Not as good as the next heuristic: + // Greater size in at least one dim. + static bool Greater( const TRect& a, const TRect& b ) + { + return (a.w > b.w && a.w > b.h) || (a.h > b.w && a.h > b.h); + } + }; + + // --------------------- + + typedef std::vector CPosArray; + typedef std::vector CRectArray; + + // --------------------- + + CRectPlacement() { Init(); } + ~CRectPlacement() { End(); } + + void Init( int w = 1, int h = 1 ); + void End(); + + bool IsOk() const { return m_size.w > 0; } + + int GetW() const { return m_size.w; } + int GetH() const { return m_size.h; } + double GetArea() const { return m_area; } + double GetTotalArea() const { return (double)m_size.w * m_size.h; } + + bool AddAtEmptySpotAutoGrow( TRect* pRect, int maxW, int maxH ); + +private: + TRect m_size; + CRectArray m_vRects; + CPosArray m_vPositions; + double m_area; + + // --------------------- + + bool IsFree( const TRect& r ) const; + void AddPosition( const TPos& p ); + void AddRect( const TRect& r ); + bool AddAtEmptySpot( TRect& r ); +}; + +#endif // _RECT_PLACEMENT_H_ diff --git a/pcbnew/autorouter/solve.cpp b/pcbnew/autorouter/solve.cpp index c3c174d352..c06d8545eb 100644 --- a/pcbnew/autorouter/solve.cpp +++ b/pcbnew/autorouter/solve.cpp @@ -324,7 +324,6 @@ int PCB_EDIT_FRAME::Solve( wxDC* DC, int aLayersCount ) AppendMsgPanel( wxT( "Activity" ), msg, BROWN ); } - pt_cur_ch = pt_cur_ch; segm_oX = GetBoard()->GetBoundingBox().GetX() + (RoutingMatrix.m_GridRouting * col_source); segm_oY = GetBoard()->GetBoundingBox().GetY() + (RoutingMatrix.m_GridRouting * row_source); segm_fX = GetBoard()->GetBoundingBox().GetX() + (RoutingMatrix.m_GridRouting * col_target); diff --git a/pcbnew/autorouter/spread_footprints.cpp b/pcbnew/autorouter/spread_footprints.cpp new file mode 100644 index 0000000000..ee9ea8a6cb --- /dev/null +++ b/pcbnew/autorouter/spread_footprints.cpp @@ -0,0 +1,356 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2013 Jean-Pierre Charras, jean-pierre.charras@ujf-grenoble.fr + * Copyright (C) 2013 SoftPLC Corporation, Dick Hollenbeck + * Copyright (C) 2013 Wayne Stambaugh + * + * Copyright (C) 1992-2013 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 + */ + +/** + * @file spread_footprints.cpp + * @brief functions to spread footprints on free areas outside a board. + * this is usefull after reading a netlist, when new footprints are loaded + * and stacked at 0,0 coordinate. + * Often, spread them on a free area near the board being edited make more easy + * their selection. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +struct TSubRect : public CRectPlacement::TRect +{ + int n; // Original index of this subrect, before sorting + + TSubRect() { } + TSubRect( int _w, int _h, int _n ) : + TRect( 0, 0, _w, _h ), n( _n ) { } +}; + +typedef std::vector CSubRectArray; + +// Use 0.01 mm units to calculate placement, to avoid long calculation time +const int scale = (int)(0.01 * IU_PER_MM); + +// Populates a list of rectangles, from a list of modules +void fillRectList( CSubRectArray& vecSubRects, std::vector & aModuleList ) +{ + vecSubRects.clear(); + + for( unsigned ii = 0; ii < aModuleList.size(); ii++ ) + { + EDA_RECT fpBox = aModuleList[ii]->GetBoundingBox(); + TSubRect fpRect( fpBox.GetWidth()/scale, fpBox.GetHeight()/scale, ii ); + vecSubRects.push_back( fpRect ); + } +} + +// Populates a list of rectangles, from a list of EDA_RECT +void fillRectList( CSubRectArray& vecSubRects, std::vector & aRectList ) +{ + vecSubRects.clear(); + + for( unsigned ii = 0; ii < aRectList.size(); ii++ ) + { + EDA_RECT& rect = aRectList[ii]; + TSubRect fpRect( rect.GetWidth()/scale, rect.GetHeight()/scale, ii ); + vecSubRects.push_back( fpRect ); + } +} + + + +// Spread a list of rectangles inside a placement area +void spreadRectangles( CRectPlacement& aPlacementArea, + CSubRectArray& vecSubRects, + int areaSizeX, int areaSizeY ) +{ + areaSizeX/= scale; + areaSizeY/= scale; + + // Sort the subRects based on dimensions, larger dimension goes first. + std::sort( vecSubRects.begin(), vecSubRects.end(), CRectPlacement::TRect::Greater ); + + // gives the initial size to the area + aPlacementArea.Init( areaSizeX, areaSizeY ); + + // Add all subrects + CSubRectArray::iterator it; + for( it = vecSubRects.begin(); it != vecSubRects.end(); ) + { + CRectPlacement::TRect r( 0, 0, it->w, it->h ); + + bool bPlaced = aPlacementArea.AddAtEmptySpotAutoGrow( &r, areaSizeX, areaSizeY ); + + if( !bPlaced ) // No room to place the rectangle: enlarge area and retry + { + areaSizeX = ceil(areaSizeX * 1.1); + areaSizeY = ceil(areaSizeY * 1.1); + aPlacementArea.Init( areaSizeX, areaSizeY ); + it = vecSubRects.begin(); + continue; + } + + // When correctly placed in a placement area, the coords are returned in r.x and r.y + // Store them. + it->x = r.x; + it->y = r.y; + + it++; + } +} + + +void moveFootprintsInArea( CRectPlacement& aPlacementArea, + std::vector & aModuleList, EDA_RECT& aFreeArea, + bool aFindAreaOnly ) +{ + CSubRectArray vecSubRects; + + fillRectList( vecSubRects, aModuleList ); + spreadRectangles( aPlacementArea, vecSubRects, + aFreeArea.GetWidth(), aFreeArea.GetHeight() ); + + if( aFindAreaOnly ) + return; + + for( unsigned it = 0; it < vecSubRects.size(); ++it ) + { + wxPoint pos( vecSubRects[it].x, vecSubRects[it].y ); + pos.x *= scale; + pos.y *= scale; + + MODULE * module = aModuleList[vecSubRects[it].n]; + + EDA_RECT fpBBox = module->GetBoundingBox(); + wxPoint mod_pos = pos + ( module->GetPosition() - fpBBox.GetOrigin() ) + + aFreeArea.GetOrigin(); + + module->Move( mod_pos - module->GetPosition() ); + } +} + +static bool sortModulesbySheetPath( MODULE* ref, MODULE* compare ); + +/* Function to move components in a rectangular area format 4 / 3, + * starting from the mouse cursor + * The components with the FIXED status set are not moved + */ +void PCB_EDIT_FRAME::SpreadFootprints( bool aFootprintsOutsideBoardOnly ) +{ + EDA_RECT bbox = GetBoard()->ComputeBoundingBox( true ); + bool edgesExist = ( bbox.GetWidth() || bbox.GetHeight() ); + + // no edges exist + if( aFootprintsOutsideBoardOnly && !edgesExist ) + { + DisplayError( this, + _( "Could not automatically place modules. No board outlines detected." ) ); + return; + } + + // if aFootprintsOutsideBoardOnly is true, and if board outline exists, + // wue have to filter footprints to move: + bool outsideBrdFilter = aFootprintsOutsideBoardOnly && edgesExist; + + // Build candidate list + // calculate also the area needed by these footprints + MODULE* Module = GetBoard()->m_Modules; + std::vector moduleList; + + for( ; Module != NULL; Module = Module->Next() ) + { + Module->CalculateBoundingBox(); + + if( outsideBrdFilter ) + { + if( bbox.Contains( Module->GetPosition() ) ) + continue; + } + + if( Module->IsLocked() ) + continue; + + moduleList.push_back(Module); + } + + if( moduleList.size() == 0 ) // Nothing to do + return; + + // sort footprints by sheet path. we group them later by sheet + sort( moduleList.begin(), moduleList.end(), sortModulesbySheetPath ); + + // Undo command: init undo list + PICKED_ITEMS_LIST undoList; + undoList.m_Status = UR_CHANGED; + ITEM_PICKER picker( NULL, UR_CHANGED ); + + for( unsigned ii = 0; ii < moduleList.size(); ii++ ) + { + Module = moduleList[ii]; + + // Undo: add copy of module to undo list + picker.SetItem( Module ); + picker.SetLink( Module->Clone() ); + undoList.PushItem( picker ); + } + + // Extract and place footprints by sheet + std::vector moduleListBySheet; + std::vector placementSheetAreas; + wxString curr_sheetPath ; + double subsurface; + double placementsurface = 0.0; + + wxPoint placementAreaPosition = GetCrossHairPosition(); + + // We do not want to move footprints inside an existing board. + // move the placement area position outside the board bounding box + // to the left of the board + if( edgesExist ) + { + if( placementAreaPosition.x < bbox.GetEnd().x && + placementAreaPosition.y < bbox.GetEnd().y ) + { + placementAreaPosition.x = bbox.GetEnd().x; + placementAreaPosition.y = bbox.GetOrigin().y; + } + } + + // The placement uses 2 passes: + // the first pass creates the rectangular areas to place footprints + // each sheet in schematic creates one rectangular area. + // the second pass moves footprints inside these areas + for( int pass = 0; pass < 2; pass++ ) + { + int subareaIdx = 0; + curr_sheetPath = moduleList[0]->GetPath().BeforeLast( '/' ); + moduleListBySheet.clear(); + subsurface = 0.0; + + for( unsigned ii = 0; ii < moduleList.size(); ii++ ) + { + Module = moduleList[ii]; + bool iscurrPath = curr_sheetPath == moduleList[ii]->GetPath().BeforeLast( '/' ); + + if( iscurrPath ) + { + moduleListBySheet.push_back( Module ); + subsurface += Module->GetArea(); + } + + if( !iscurrPath || (ii == moduleList.size()-1) ) + { + // end of the footprint sublist relative to the same sheet path + // calculate placement of the current sublist + EDA_RECT freeArea; + int Xsize_allowed = (int) ( sqrt( subsurface ) * 4.0 / 3.0 ); + int Ysize_allowed = (int) ( subsurface / Xsize_allowed ); + + freeArea.SetWidth( Xsize_allowed ); + freeArea.SetHeight( Ysize_allowed ); + CRectPlacement placementArea; + + if( pass == 1 ) + { + wxPoint areapos = placementSheetAreas[subareaIdx].GetOrigin() + + placementAreaPosition; + freeArea.SetOrigin( areapos ); + } + + bool findAreaOnly = pass == 0; + moveFootprintsInArea( placementArea, moduleListBySheet, + freeArea, findAreaOnly ); + + if( pass == 0 ) + { + // Populate sheet placement areas list + EDA_RECT sub_area; + sub_area.SetWidth( placementArea.GetW()*scale ); + sub_area.SetHeight( placementArea.GetH()*scale ); + // Add a margin around the sheet placement area: + sub_area.Inflate( Millimeter2iu( 1.5 ) ); + + placementSheetAreas.push_back( sub_area ); + + placementsurface += (double) sub_area.GetWidth()* + sub_area.GetHeight(); + } + + curr_sheetPath = moduleList[ii]->GetPath().BeforeLast( '/' ); + subsurface = 0.0; + moduleListBySheet.clear(); + + // Enter first module of next sheet + moduleListBySheet.push_back( Module ); + subsurface += Module->GetArea(); + + subareaIdx++; + } + } + + // End of pass: + // At the end of the first pass, we have to find position of each sheet + // placement area + if( pass == 0 ) + { + int Xsize_allowed = (int) ( sqrt( placementsurface ) * 4.0 / 3.0 ); + int Ysize_allowed = (int) ( placementsurface / Xsize_allowed ); + CRectPlacement placementArea; + CSubRectArray vecSubRects; + + fillRectList( vecSubRects, placementSheetAreas ); + spreadRectangles( placementArea, vecSubRects, Xsize_allowed, Ysize_allowed ); + + for( unsigned it = 0; it < vecSubRects.size(); ++it ) + { + TSubRect& srect = vecSubRects[it]; + wxPoint pos( srect.x*scale, srect.y*scale ); + wxSize size( srect.w*scale, srect.h*scale ); + placementSheetAreas[srect.n].SetOrigin( pos ); + placementSheetAreas[srect.n].SetSize( size ); + } + } + } // End pass + + // Undo: commit list + SaveCopyInUndoList( undoList, UR_CHANGED ); + OnModify(); + + m_canvas->Refresh(); +} + + +static bool sortModulesbySheetPath( MODULE* ref, MODULE* compare ) +{ + return compare->GetPath().Cmp( ref->GetPath() ) < 0; +} diff --git a/pcbnew/onrightclick.cpp b/pcbnew/onrightclick.cpp index 1923608adb..37d53b3795 100644 --- a/pcbnew/onrightclick.cpp +++ b/pcbnew/onrightclick.cpp @@ -386,25 +386,26 @@ bool PCB_EDIT_FRAME::OnRightClick( const wxPoint& aMousePos, wxMenu* aPopMenu ) { wxMenu* commands = new wxMenu; AddMenuItem( aPopMenu, commands, ID_POPUP_PCB_AUTOPLACE_COMMANDS, - _( "Global Move and Place" ), KiBitmap( move_xpm ) ); + _( "Global Spread and Place" ), KiBitmap( move_xpm ) ); AddMenuItem( commands, ID_POPUP_PCB_AUTOPLACE_FREE_ALL_MODULES, - _( "Unlock All Modules" ), KiBitmap( unlocked_xpm ) ); + _( "Unlock All Footprints" ), KiBitmap( unlocked_xpm ) ); AddMenuItem( commands, ID_POPUP_PCB_AUTOPLACE_FIXE_ALL_MODULES, - _( "Lock All Modules" ), KiBitmap( locked_xpm ) ); + _( "Lock All Footprints" ), KiBitmap( locked_xpm ) ); commands->AppendSeparator(); - AddMenuItem( commands, ID_POPUP_PCB_AUTOMOVE_ALL_MODULES, - _( "Move All Modules" ), KiBitmap( move_xpm ) ); - commands->Append( ID_POPUP_PCB_AUTOMOVE_NEW_MODULES, _( "Move New Modules" ) ); + AddMenuItem( commands, ID_POPUP_PCB_SPREAD_ALL_MODULES, + _( "Spread out All Footprints" ), KiBitmap( move_xpm ) ); + commands->Append( ID_POPUP_PCB_SPREAD_NEW_MODULES, + _( "Spread out Footprints not Already on Board" ) ); commands->AppendSeparator(); commands->Append( ID_POPUP_PCB_AUTOPLACE_ALL_MODULES, - _( "Automatically Place All Modules" ) ); + _( "Automatically Place All Footprints" ) ); commands->Append( ID_POPUP_PCB_AUTOPLACE_NEW_MODULES, - _( "Automatically Place New Modules" ) ); + _( "Automatically Place New Footprints" ) ); commands->Append( ID_POPUP_PCB_AUTOPLACE_NEXT_MODULE, - _( "Automatically Place Next Module" ) ); + _( "Automatically Place Next Footprints" ) ); commands->AppendSeparator(); AddMenuItem( commands, ID_POPUP_PCB_REORIENT_ALL_MODULES, - _( "Orient All Modules" ), KiBitmap( rotate_module_pos_xpm ) ); + _( "Orient All Footprints" ), KiBitmap( rotate_module_pos_xpm ) ); aPopMenu->AppendSeparator(); } diff --git a/pcbnew/pcbframe.cpp b/pcbnew/pcbframe.cpp index 7e415cd831..b6a2a2c2d5 100644 --- a/pcbnew/pcbframe.cpp +++ b/pcbnew/pcbframe.cpp @@ -1,10 +1,10 @@ /* * This program source code file is part of KiCad, a free EDA CAD application. * - * Copyright (C) 2004-2010 Jean-Pierre Charras, jean-pierre.charras@gpisa-lab.inpg.fr - * Copyright (C) 2010 SoftPLC Corporation, Dick Hollenbeck - * Copyright (C) 2011 Wayne Stambaugh - * Copyright (C) 2010 KiCad Developers, see change_log.txt for contributors. + * Copyright (C) 2013 Jean-Pierre Charras, jp.charras at wanadoo.fr + * Copyright (C) 2013 SoftPLC Corporation, Dick Hollenbeck + * Copyright (C) 2013 Wayne Stambaugh + * Copyright (C) 2013 KiCad Developers, see change_log.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 @@ -237,7 +237,7 @@ BEGIN_EVENT_TABLE( PCB_EDIT_FRAME, PCB_BASE_FRAME ) PCB_EDIT_FRAME::ProcessMuWaveFunctions ) EVT_MENU_RANGE( ID_POPUP_PCB_AUTOPLACE_START_RANGE, ID_POPUP_PCB_AUTOPLACE_END_RANGE, - PCB_EDIT_FRAME::AutoPlace ) + PCB_EDIT_FRAME::OnPlaceOrRouteFootprints ) EVT_MENU( ID_POPUP_PCB_REORIENT_ALL_MODULES, PCB_EDIT_FRAME::OnOrientFootprints ) diff --git a/pcbnew/pcbnew_id.h b/pcbnew/pcbnew_id.h index 4534b9d18c..1a03a3764c 100644 --- a/pcbnew/pcbnew_id.h +++ b/pcbnew/pcbnew_id.h @@ -216,8 +216,8 @@ enum pcbnew_ids ID_POPUP_PCB_AUTOPLACE_FREE_ALL_MODULES, ID_POPUP_PCB_AUTOPLACE_FIXE_ALL_MODULES, ID_POPUP_PCB_AUTOPLACE_CURRENT_MODULE, - ID_POPUP_PCB_AUTOMOVE_ALL_MODULES, - ID_POPUP_PCB_AUTOMOVE_NEW_MODULES, + ID_POPUP_PCB_SPREAD_ALL_MODULES, + ID_POPUP_PCB_SPREAD_NEW_MODULES, ID_POPUP_PCB_AUTOPLACE_COMMANDS, ID_POPUP_PCB_AUTOPLACE_ALL_MODULES, ID_POPUP_PCB_AUTOPLACE_NEW_MODULES,