From 073a9e17240c2496d7278fd751f570f70991bf37 Mon Sep 17 00:00:00 2001 From: Lorenzo Marcantonio Date: Sat, 17 May 2014 19:36:02 +0200 Subject: [PATCH 1/2] TRACK::GetTrack can now be told to confine search to the netlist and/or force the sequential (restartable) algorithm Reworked the collinear track routines. Cleanup should be faster given the above modification. --- pcbnew/class_track.cpp | 158 +++++++++----------------------- pcbnew/class_track.h | 7 +- pcbnew/clean.cpp | 166 ++++++++++------------------------ pcbnew/move_or_drag_track.cpp | 8 +- 4 files changed, 104 insertions(+), 235 deletions(-) diff --git a/pcbnew/class_track.cpp b/pcbnew/class_track.cpp index 130104e0b1..ace8ca96ef 100644 --- a/pcbnew/class_track.cpp +++ b/pcbnew/class_track.cpp @@ -66,28 +66,6 @@ static bool ShowClearance( const TRACK* aTrack ) } -/* - * return true if the dist between p1 and p2 < max_dist - * Currently in test (currently ratsnest algos work only if p1 == p2) - */ -inline bool IsNear( const wxPoint& p1, const wxPoint& p2, int max_dist ) -{ -#if 0 // Do not change it: does not work - int dist; - dist = abs( p1.x - p2.x ) + abs( p1.y - p2.y ); - dist *= 7; - dist /= 10; - - if ( dist < max_dist ) - return true; -#else - if ( p1 == p2 ) - return true; -#endif - return false; -} - - TRACK* GetTrack( TRACK* aStartTrace, const TRACK* aEndTrace, const wxPoint& aPosition, LAYER_MSK aLayerMask ) { @@ -1303,125 +1281,79 @@ VIA* TRACK::GetVia( TRACK* aEndTrace, const wxPoint& aPosition, LAYER_MSK aLayer } -TRACK* TRACK::GetTrack( TRACK* aStartTrace, TRACK* aEndTrace, ENDPOINT_T aEndPoint ) +TRACK* TRACK::GetTrack( TRACK* aStartTrace, TRACK* aEndTrace, ENDPOINT_T aEndPoint, + bool aSameNetOnly, bool aSequential ) { - const int NEIGHTBOUR_COUNT_MAX = 50; - - TRACK* previousSegment; - TRACK* nextSegment; - int Reflayer; - int ii; - int max_dist; const wxPoint &position = GetEndPoint( aEndPoint ); + LAYER_MSK refLayers = GetLayerMask(); + TRACK *previousSegment; + TRACK *nextSegment; - Reflayer = GetLayerMask(); - - previousSegment = nextSegment = this; - - // Local search: - for( ii = 0; ii < NEIGHTBOUR_COUNT_MAX; ii++ ) + if( aSequential ) { - if( (nextSegment == NULL) && (previousSegment == NULL) ) - break; + // Simple sequential search: from aStartTrace forward to aEndTrace + previousSegment = NULL; + nextSegment = aStartTrace; + } + else + { + /* Local bidirectional search: from this backward to aStartTrace + * AND forward to aEndTrace. The idea is that nearest segments + * are found (on average) faster in this way. In fact same-net + * segments are almost guaranteed to be found faster, in a global + * search, since they are grouped together in the track list */ + previousSegment = this; + nextSegment = this; + } + + while( nextSegment || previousSegment ) + { + // Terminate the search in the direction if the netcode mismatches + if( aSameNetOnly ) + { + if( nextSegment && (nextSegment->GetNetCode() != GetNetCode()) ) + nextSegment = NULL; + if( previousSegment && (previousSegment->GetNetCode() != GetNetCode()) ) + previousSegment = NULL; + } if( nextSegment ) { - if( nextSegment->GetState( BUSY | IS_DELETED ) ) - goto suite; - - if( nextSegment == this ) - goto suite; - - /* max_dist is the max distance between 2 track ends which - * ensure a copper continuity */ - max_dist = ( nextSegment->m_Width + this->m_Width ) / 2; - - if( IsNear( position, nextSegment->m_Start, max_dist ) ) + if ( (nextSegment != this) && + !nextSegment->GetState( BUSY | IS_DELETED ) && + (refLayers & nextSegment->GetLayerMask()) ) { - if( Reflayer & nextSegment->GetLayerMask() ) + if( (position == nextSegment->m_Start) || + (position == nextSegment->m_End) ) return nextSegment; } - if( IsNear( position, nextSegment->m_End, max_dist ) ) - { - if( Reflayer & nextSegment->GetLayerMask() ) - return nextSegment; - } -suite: + // Keep looking forward if( nextSegment == aEndTrace ) nextSegment = NULL; else - nextSegment = nextSegment->Next(); + nextSegment = nextSegment->Next(); } + // Same as above, looking back. During sequential search this branch is inactive if( previousSegment ) { - if( previousSegment->GetState( BUSY | IS_DELETED ) ) - goto suite1; - - if( previousSegment == this ) - goto suite1; - - max_dist = ( previousSegment->m_Width + m_Width ) / 2; - - if( IsNear( position, previousSegment->m_Start, max_dist ) ) + if ( (previousSegment != this) && + !previousSegment->GetState( BUSY | IS_DELETED ) && + (refLayers & previousSegment->GetLayerMask()) ) { - if( Reflayer & previousSegment->GetLayerMask() ) + if( (position == previousSegment->m_Start) || + (position == previousSegment->m_End) ) return previousSegment; } - if( IsNear( position, previousSegment->m_End, max_dist ) ) - { - if( Reflayer & previousSegment->GetLayerMask() ) - return previousSegment; - } -suite1: if( previousSegment == aStartTrace ) previousSegment = NULL; - else if( previousSegment->Type() != PCB_T ) - previousSegment = previousSegment->Back(); else - previousSegment = NULL; + previousSegment = previousSegment->Back(); } } - // General search - for( nextSegment = aStartTrace; nextSegment != NULL; nextSegment = nextSegment->Next() ) - { - if( nextSegment->GetState( IS_DELETED | BUSY ) ) - { - if( nextSegment == aEndTrace ) - break; - - continue; - } - - if( nextSegment == this ) - { - if( nextSegment == aEndTrace ) - break; - - continue; - } - - max_dist = ( nextSegment->m_Width + m_Width ) / 2; - - if( IsNear( position, nextSegment->m_Start, max_dist ) ) - { - if( Reflayer & nextSegment->GetLayerMask() ) - return nextSegment; - } - - if( IsNear( position, nextSegment->m_End, max_dist ) ) - { - if( Reflayer & nextSegment->GetLayerMask() ) - return nextSegment; - } - - if( nextSegment == aEndTrace ) - break; - } - return NULL; } diff --git a/pcbnew/class_track.h b/pcbnew/class_track.h index c5ca9d03d7..58994bdff0 100644 --- a/pcbnew/class_track.h +++ b/pcbnew/class_track.h @@ -261,9 +261,14 @@ public: * @param aEndTrace A pointer to the TRACK object to stop the search. A NULL value * searches to the end of the list. * @param aEndPoint The start or end point of the segment to test against. + * @param aSameNetOnly if true stop searching when the netcode changes + * @param aSequential If true, forces a forward sequential search, + * which is restartable; the default search can be faster but the + * position of the returned track in the list is unpredictable * @return A TRACK object pointer if found otherwise NULL. */ - TRACK* GetTrack( TRACK* aStartTrace, TRACK* aEndTrace, ENDPOINT_T aEndPoint ); + TRACK* GetTrack( TRACK* aStartTrace, TRACK* aEndTrace, ENDPOINT_T aEndPoint, + bool aSameNetOnly, bool aSequential ); /** * Function GetEndSegments diff --git a/pcbnew/clean.cpp b/pcbnew/clean.cpp index fce7fd632c..ecf08d4012 100644 --- a/pcbnew/clean.cpp +++ b/pcbnew/clean.cpp @@ -303,7 +303,7 @@ bool TRACKS_CLEANER::testTrackEndpointDangling( TRACK *aTrack, ENDPOINT_T aEndPo { bool flag_erase = false; - TRACK* other = aTrack->GetTrack( m_Brd->m_Track, NULL, aEndPoint ); + TRACK* other = aTrack->GetTrack( m_Brd->m_Track, NULL, aEndPoint, true, false ); if( (other == NULL) && (zoneForTrackEndpoint( aTrack, aEndPoint ) == NULL) ) flag_erase = true; // Start endpoint is neither on pad, zone or other track @@ -324,7 +324,7 @@ bool TRACKS_CLEANER::testTrackEndpointDangling( TRACK *aTrack, ENDPOINT_T aEndPo // search for another segment following the via aTrack->SetState( BUSY, true ); - other = via->GetTrack( m_Brd->m_Track, NULL, aEndPoint ); + other = via->GetTrack( m_Brd->m_Track, NULL, aEndPoint, true, false ); // There is a via on the start but it goes nowhere if( (other == NULL) && @@ -450,95 +450,50 @@ bool TRACKS_CLEANER::remove_duplicates_of_track( const TRACK *aTrack ) bool TRACKS_CLEANER::merge_collinear_of_track( TRACK *aSegment ) { bool merged_this = false; - bool flag = false; // If there are connections to this on the endpoint - // search for a possible point connected to the START point of the current segment - TRACK *segStart = aSegment->Next(); - while( true ) + // *WHY* doesn't C++ have prec and succ (or ++ --) like PASCAL? + for( ENDPOINT_T endpoint = ENDPOINT_START; endpoint <= ENDPOINT_END; + endpoint = ENDPOINT_T( endpoint + 1 ) ) { - segStart = aSegment->GetTrack( segStart, NULL, ENDPOINT_START ); - - if( segStart ) + // search for a possible segment connected to the current endpoint of the current one + TRACK *other = aSegment->Next(); + if( other ) { - // the two segments must have the same width - if( aSegment->GetWidth() != segStart->GetWidth() ) - break; + other = aSegment->GetTrack( other, NULL, endpoint, true, false ); - // it cannot be a via - if( segStart->Type() != PCB_TRACE_T ) - break; + if( other ) + { + // the two segments must have the same width and the other + // cannot be a via + if( (aSegment->GetWidth() == other->GetWidth()) && + (other->Type() == PCB_TRACE_T) ) + { + // There can be only one segment connected + other->SetState( BUSY, true ); + TRACK *yet_another = aSegment->GetTrack( m_Brd->m_Track, NULL, + endpoint, true, false ); + other->SetState( BUSY, false ); - // We must have only one segment connected - segStart->SetState( BUSY, true ); - TRACK *other = aSegment->GetTrack( m_Brd->m_Track, NULL, ENDPOINT_START ); - segStart->SetState( BUSY, false ); + if( !yet_another ) + { + // Try to merge them + TRACK *segDelete = mergeCollinearSegmentIfPossible( aSegment, + other, endpoint ); - if( other == NULL ) - flag = true; // OK - - break; - } - break; - } - - if( flag ) // We have the starting point of the segment is connected to an other segment - { - TRACK *segDelete = mergeCollinearSegmentIfPossible( aSegment, segStart, ENDPOINT_START ); - - if( segDelete ) - { - m_Brd->GetRatsnest()->Remove( segDelete ); - segDelete->ViewRelease(); - segDelete->DeleteStructure(); - merged_this = true; + // Merge succesful, the other one has to go away + if( segDelete ) + { + m_Brd->GetRatsnest()->Remove( segDelete ); + segDelete->ViewRelease(); + segDelete->DeleteStructure(); + merged_this = true; + } + } + } + } } } - // Do the same with the other endpoint - flag = false; - - // search for a possible point connected to the END point of the current segment: - TRACK *segEnd = aSegment->Next(); - while( true ) - { - segEnd = aSegment->GetTrack( segEnd, NULL, ENDPOINT_END ); - - if( segEnd ) - { - if( aSegment->GetWidth() != segEnd->GetWidth() ) - break; - - if( segEnd->Type() != PCB_TRACE_T ) - break; - - // We must have only one segment connected - segEnd->SetState( BUSY, true ); - TRACK *other = aSegment->GetTrack( m_Brd->m_Track, NULL, ENDPOINT_END ); - segEnd->SetState( BUSY, false ); - - if( other == NULL ) - flag = true; // Ok - - break; - } - else - { - break; - } - } - - if( flag ) // We have the ending point of the segment is connected to an other segment - { - TRACK *segDelete = mergeCollinearSegmentIfPossible( aSegment, segEnd, ENDPOINT_END ); - - if( segDelete ) - { - m_Brd->GetRatsnest()->Remove( segDelete ); - segDelete->ViewRelease(); - segDelete->DeleteStructure(); - merged_this = true; - } - } return merged_this; } @@ -550,12 +505,9 @@ bool TRACKS_CLEANER::clean_segments() // Easy things first modified |= delete_null_segments(); - // Delete redundant segments, i.e. segments having the same end points - // and layers + // Delete redundant segments, i.e. segments having the same end points and layers for( TRACK *segment = m_Brd->m_Track; segment; segment = segment->Next() ) - { modified |= remove_duplicates_of_track( segment ); - } // merge collinear segments: TRACK *nextsegment; @@ -568,7 +520,7 @@ bool TRACKS_CLEANER::clean_segments() bool merged_this = merge_collinear_of_track( segment ); modified |= merged_this; - if( merged_this ) // The current segment was modified, retry to merge it + if( merged_this ) // The current segment was modified, retry to merge it again nextsegment = segment->Next(); } } @@ -579,44 +531,24 @@ bool TRACKS_CLEANER::clean_segments() /* Utility: check for parallelism between two segments */ static bool parallelism_test( int dx1, int dy1, int dx2, int dy2 ) { - // The following condition tree is ugly and repetitive, but I have - // not a better way to express clearly the trivial cases. Hope the - // compiler optimize it better than always doing the product - // below... + /* The following condition list is ugly and repetitive, but I have + * not a better way to express clearly the trivial cases. Hope the + * compiler optimize it better than always doing the product + * below... */ // test for vertical alignment (easy to handle) if( dx1 == 0 ) - { - if( dx2 != 0 ) - return false; - else - return true; - } + return dx2 == 0; if( dx2 == 0 ) - { - if( dx1 != 0 ) - return false; - else - return true; - } + return dx1 == 0; // test for horizontal alignment (easy to handle) if( dy1 == 0 ) - { - if( dy2 != 0 ) - return false; - else - return true; - } + return dy2 == 0; if( dy2 == 0 ) - { - if( dy1 != 0 ) - return false; - else - return true; - } + return dy1 == 0; /* test for alignment in other cases: Do the usual cross product test * (the same as testing the slope, but without a division) */ @@ -746,7 +678,7 @@ bool PCB_EDIT_FRAME::RemoveMisConnectedTracks() } else { - other = segment->GetTrack( GetBoard()->m_Track, NULL, ENDPOINT_START ); + other = segment->GetTrack( GetBoard()->m_Track, NULL, ENDPOINT_START, false, false ); if( other ) net_code_s = other->GetNetCode(); @@ -764,7 +696,7 @@ bool PCB_EDIT_FRAME::RemoveMisConnectedTracks() } else { - other = segment->GetTrack( GetBoard()->m_Track, NULL, ENDPOINT_END ); + other = segment->GetTrack( GetBoard()->m_Track, NULL, ENDPOINT_END, false, false ); if( other ) net_code_e = other->GetNetCode(); diff --git a/pcbnew/move_or_drag_track.cpp b/pcbnew/move_or_drag_track.cpp index 6631ce31dc..8156764d9b 100644 --- a/pcbnew/move_or_drag_track.cpp +++ b/pcbnew/move_or_drag_track.cpp @@ -709,7 +709,7 @@ void PCB_EDIT_FRAME::Start_DragTrackSegmentAndKeepSlope( TRACK* track, wxDC* DC s_StartSegmentPresent = s_EndSegmentPresent = true; if( ( track->start == NULL ) || ( track->start->Type() == PCB_TRACE_T ) ) - TrackToStartPoint = track->GetTrack( GetBoard()->m_Track, NULL, ENDPOINT_START ); + TrackToStartPoint = track->GetTrack( GetBoard()->m_Track, NULL, ENDPOINT_START, true, false ); // Test if more than one segment is connected to this point if( TrackToStartPoint ) @@ -717,14 +717,14 @@ void PCB_EDIT_FRAME::Start_DragTrackSegmentAndKeepSlope( TRACK* track, wxDC* DC TrackToStartPoint->SetState( BUSY, true ); if( ( TrackToStartPoint->Type() == PCB_VIA_T ) - || track->GetTrack( GetBoard()->m_Track, NULL, ENDPOINT_START ) ) + || track->GetTrack( GetBoard()->m_Track, NULL, ENDPOINT_START, true, false ) ) error = true; TrackToStartPoint->SetState( BUSY, false ); } if( ( track->end == NULL ) || ( track->end->Type() == PCB_TRACE_T ) ) - TrackToEndPoint = track->GetTrack( GetBoard()->m_Track, NULL, ENDPOINT_END ); + TrackToEndPoint = track->GetTrack( GetBoard()->m_Track, NULL, ENDPOINT_END, true, false ); // Test if more than one segment is connected to this point if( TrackToEndPoint ) @@ -732,7 +732,7 @@ void PCB_EDIT_FRAME::Start_DragTrackSegmentAndKeepSlope( TRACK* track, wxDC* DC TrackToEndPoint->SetState( BUSY, true ); if( (TrackToEndPoint->Type() == PCB_VIA_T) - || track->GetTrack( GetBoard()->m_Track, NULL, ENDPOINT_END ) ) + || track->GetTrack( GetBoard()->m_Track, NULL, ENDPOINT_END, true, false ) ) error = true; TrackToEndPoint->SetState( BUSY, false ); From d54ade94036e03630016f5d005e9ca473233becb Mon Sep 17 00:00:00 2001 From: jean-pierre charras Date: Sat, 17 May 2014 21:29:15 +0200 Subject: [PATCH 2/2] Rework on DXF export. --- 3d-viewer/3d_draw.cpp | 2 +- common/gr_basic.cpp | 2 +- ...board_items_to_polygon_shape_transform.cpp | 106 ++++++++++--- pcbnew/class_board.h | 13 ++ pcbnew/class_pad.cpp | 33 +++++ pcbnew/class_pad.h | 11 ++ pcbnew/class_pad_draw_functions.cpp | 28 +--- pcbnew/pcbplot.h | 11 ++ pcbnew/plot_board_layers.cpp | 139 +++++++++++++++++- 9 files changed, 292 insertions(+), 53 deletions(-) diff --git a/3d-viewer/3d_draw.cpp b/3d-viewer/3d_draw.cpp index 289d3c3c72..82abd0b0de 100644 --- a/3d-viewer/3d_draw.cpp +++ b/3d-viewer/3d_draw.cpp @@ -239,7 +239,7 @@ void EDA_3D_CANVAS::BuildBoard3DView() BOARD* pcb = GetBoard(); bool realistic_mode = g_Parm_3D_Visu.IsRealisticMode(); - // Number of segments to draw a circle using segments + // Number of segments to convert a circle to polygon const int segcountforcircle = 16; double correctionFactor = 1.0 / cos( M_PI / (segcountforcircle * 2) ); const int segcountLowQuality = 12; // segments to draw a circle with low quality diff --git a/common/gr_basic.cpp b/common/gr_basic.cpp index c97ec7d8dc..b0ff4b6ff2 100644 --- a/common/gr_basic.cpp +++ b/common/gr_basic.cpp @@ -848,7 +848,7 @@ static void GRSClosedPoly( EDA_RECT* aClipBox, wxDC* aDC, // Close the polygon if( aPoints[lastpt] != aPoints[0] ) { - GRLineTo( aClipBox, aDC, aPoints[lastpt].x, aPoints[lastpt].y, aWidth, aColor ); + GRLineTo( aClipBox, aDC, aPoints[0].x, aPoints[0].y, aWidth, aColor ); } } } diff --git a/pcbnew/board_items_to_polygon_shape_transform.cpp b/pcbnew/board_items_to_polygon_shape_transform.cpp index e08cceec96..d4122e7b93 100644 --- a/pcbnew/board_items_to_polygon_shape_transform.cpp +++ b/pcbnew/board_items_to_polygon_shape_transform.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -38,6 +39,80 @@ static void addTextSegmToPoly( int x0, int y0, int xf, int yf ) s_textCircle2SegmentCount, s_textWidth ); } +/** + * Function ConvertBrdLayerToPolygonalContours + * Build a set of polygons which are the outlines of copper items + * (pads, tracks, texts, zones) + * the holes in vias or pads are ignored + * Usefull to export the shape of copper layers to dxf polygons + * or 3D viewer + * the polygons are not merged. + * @param aLayer = A layer, like LAYER_N_BACK, etc. + * @param aOutlines The CPOLYGONS_LIST to fill in with main outlines. + * @return true if success, false if a contour is not valid + */ +void BOARD::ConvertBrdLayerToPolygonalContours( LAYER_NUM aLayer, CPOLYGONS_LIST& aOutlines ) +{ + // Number of segments to convert a circle to a polygon + const int segcountforcircle = 16; + double correctionFactor = 1.0 / cos( M_PI / (segcountforcircle * 2) ); + + // convert tracks and vias: + for( TRACK* track = m_Track; track != NULL; track = track->Next() ) + { + if( !track->IsOnLayer( aLayer ) ) + continue; + + track->TransformShapeWithClearanceToPolygon( aOutlines, + 0, segcountforcircle, correctionFactor ); + } + + // convert pads + for( MODULE* module = m_Modules; module != NULL; module = module->Next() ) + { + module->TransformPadsShapesWithClearanceToPolygon( aLayer, + aOutlines, 0, segcountforcircle, correctionFactor ); + + // Micro-wave modules may have items on copper layers + module->TransformGraphicShapesWithClearanceToPolygonSet( aLayer, + aOutlines, 0, segcountforcircle, correctionFactor ); + } + + // convert copper zones + for( int ii = 0; ii < GetAreaCount(); ii++ ) + { + ZONE_CONTAINER* zone = GetArea( ii ); + LAYER_NUM zonelayer = zone->GetLayer(); + + if( zonelayer == aLayer ) + zone->TransformSolidAreasShapesToPolygonSet( + aOutlines, segcountforcircle, correctionFactor ); + } + + // convert graphic items on copper layers (texts) + for( BOARD_ITEM* item = m_Drawings; item; item = item->Next() ) + { + if( !item->IsOnLayer( aLayer ) ) + continue; + + switch( item->Type() ) + { + case PCB_LINE_T: // should not exist on copper layers + ( (DRAWSEGMENT*) item )->TransformShapeWithClearanceToPolygon( + aOutlines, 0, segcountforcircle, correctionFactor ); + break; + + case PCB_TEXT_T: + ( (TEXTE_PCB*) item )->TransformShapeWithClearanceToPolygonSet( + aOutlines, 0, segcountforcircle, correctionFactor ); + break; + + default: + break; + } + } +} + /* generate pads shapes on layer aLayer as polygons, * and adds these polygons to aCornerBuffer * aCornerBuffer = the buffer to store polygons @@ -614,43 +689,26 @@ bool D_PAD::BuildPadDrillShapePolygon( CPOLYGONS_LIST& aCornerBuffer, int aInflateValue, int aSegmentsPerCircle ) const { wxSize drillsize = GetDrillSize(); - bool hasHole = drillsize.x && drillsize.y; - if( ! hasHole ) + if( !drillsize.x || !drillsize.y ) return false; - drillsize.x += aInflateValue; - drillsize.y += aInflateValue; - if( drillsize.x == drillsize.y ) // usual round hole { TransformCircleToPolygon( aCornerBuffer, GetPosition(), - drillsize.x /2, aSegmentsPerCircle ); + (drillsize.x / 2) + aInflateValue, aSegmentsPerCircle ); } else // Oblong hole { - wxPoint ends_offset; + wxPoint start, end; int width; - if( drillsize.x > drillsize.y ) // Horizontal oval - { - ends_offset.x = ( drillsize.x - drillsize.y ) / 2; - width = drillsize.y; - } - else // Vertical oval - { - ends_offset.y = ( drillsize.y - drillsize.x ) / 2; - width = drillsize.x; - } + GetOblongDrillGeometry( start, end, width ); - RotatePoint( &ends_offset, GetOrientation() ); + width += aInflateValue * 2; - wxPoint start = GetPosition() + ends_offset; - wxPoint end = GetPosition() - ends_offset; - - // Prepare the shape creation - TransformRoundedEndsSegmentToPolygon( aCornerBuffer, start, end, - aSegmentsPerCircle, width ); + TransformRoundedEndsSegmentToPolygon( aCornerBuffer, + GetPosition() + start, GetPosition() + end, aSegmentsPerCircle, width ); } return true; diff --git a/pcbnew/class_board.h b/pcbnew/class_board.h index 308f69a13d..0c625cb66d 100644 --- a/pcbnew/class_board.h +++ b/pcbnew/class_board.h @@ -685,6 +685,19 @@ public: CPOLYGONS_LIST& aHoles, wxString* aErrorText = NULL ); + /** + * Function ConvertBrdLayerToPolygonalContours + * Build a set of polygons which are the outlines of copper items + * (pads, tracks, vias, texts, zones) + * Holes in vias or pads are ignored + * Usefull to export the shape of copper layers to dxf polygons + * or 3D viewer + * the polygons are not merged. + * @param aLayer = A copper layer, like LAYER_N_BACK, etc. + * @param aOutlines The CPOLYGONS_LIST to fill in with items outline. + */ + void ConvertBrdLayerToPolygonalContours( LAYER_NUM aLayer, CPOLYGONS_LIST& aOutlines ); + /** * Function GetLayerName * returns the name of a layer given by aLayer. Copper layers may diff --git a/pcbnew/class_pad.cpp b/pcbnew/class_pad.cpp index f4a1282ed7..2d2342d4e8 100644 --- a/pcbnew/class_pad.cpp +++ b/pcbnew/class_pad.cpp @@ -637,6 +637,39 @@ bool D_PAD::IsOnLayer( LAYER_NUM aLayer ) const } +void D_PAD::GetOblongDrillGeometry( wxPoint& aStartPoint, + wxPoint& aEndPoint, int& aWidth ) const +{ + // calculates the start point, end point and width + // of an equivalent segment which have the same position and width as the hole + int delta_cx, delta_cy; + + wxSize halfsize = GetDrillSize();; + halfsize.x /= 2; + halfsize.y /= 2; + + if( m_Drill.x > m_Drill.y ) // horizontal + { + delta_cx = halfsize.x - halfsize.y; + delta_cy = 0; + aWidth = m_Drill.y; + } + else // vertical + { + delta_cx = 0; + delta_cy = halfsize.y - halfsize.x; + aWidth = m_Drill.x; + } + + RotatePoint( &delta_cx, &delta_cy, m_Orient ); + + aStartPoint.x = delta_cx; + aStartPoint.y = delta_cy; + + aEndPoint.x = - delta_cx; + aEndPoint.y = - delta_cy; +} + bool D_PAD::HitTest( const wxPoint& aPosition ) const { int dx, dy; diff --git a/pcbnew/class_pad.h b/pcbnew/class_pad.h index 41ac5c3913..61a8c73575 100644 --- a/pcbnew/class_pad.h +++ b/pcbnew/class_pad.h @@ -169,6 +169,17 @@ public: { m_drillShape = aDrillShape; } PAD_DRILL_SHAPE_T GetDrillShape() const { return m_drillShape; } + /** + * Function GetOblongDrillGeometry calculates the start point, end point and width + * of an equivalent segment which have the same position and width as the hole + * Usefull to plot/draw oblong holes like segments with rounded ends + * used in draw and plot functions + * @param aStartPoint = first point of the equivalent segment, relative to the pad position. + * @param aEndPoint = second point of the equivalent segment, relative to the pad position. + * @param aWidth = width equivalent segment. + */ + void GetOblongDrillGeometry( wxPoint& aStartPoint, wxPoint& aEndPoint, int& aWidth ) const; + void SetLayerMask( LAYER_MSK aLayerMask ) { m_layerMask = aLayerMask; } LAYER_MSK GetLayerMask() const { return m_layerMask; } diff --git a/pcbnew/class_pad_draw_functions.cpp b/pcbnew/class_pad_draw_functions.cpp index 78fcaf457c..24113ea52a 100644 --- a/pcbnew/class_pad_draw_functions.cpp +++ b/pcbnew/class_pad_draw_functions.cpp @@ -310,7 +310,6 @@ void D_PAD::Draw( EDA_DRAW_PANEL* aPanel, wxDC* aDC, GR_DRAWMODE aDraw_mode, void D_PAD::DrawShape( EDA_RECT* aClipBox, wxDC* aDC, PAD_DRAWINFO& aDrawInfo ) { wxPoint coord[4]; - int delta_cx, delta_cy; double angle = m_Orient; int seg_width; @@ -439,27 +438,12 @@ void D_PAD::DrawShape( EDA_RECT* aClipBox, wxDC* aDC, PAD_DRAWINFO& aDrawInfo ) break; case PAD_DRILL_OBLONG: - halfsize.x = m_Drill.x >> 1; - halfsize.y = m_Drill.y >> 1; - - if( m_Drill.x > m_Drill.y ) // horizontal - { - delta_cx = halfsize.x - halfsize.y; - delta_cy = 0; - seg_width = m_Drill.y; - } - else // vertical - { - delta_cx = 0; - delta_cy = halfsize.y - halfsize.x; - seg_width = m_Drill.x; - } - - RotatePoint( &delta_cx, &delta_cy, angle ); - - GRFillCSegm( aClipBox, aDC, holepos.x + delta_cx, holepos.y + delta_cy, - holepos.x - delta_cx, holepos.y - delta_cy, seg_width, - hole_color ); + { + wxPoint drl_start, drl_end; + GetOblongDrillGeometry( drl_start, drl_end, seg_width ); + GRFilledSegment( aClipBox, aDC, holepos + drl_start, + holepos + drl_end, seg_width, hole_color ); + } break; default: diff --git a/pcbnew/pcbplot.h b/pcbnew/pcbplot.h index f42ef156bc..50c607e25f 100644 --- a/pcbnew/pcbplot.h +++ b/pcbnew/pcbplot.h @@ -204,6 +204,17 @@ void PlotOneBoardLayer( BOARD *aBoard, PLOTTER* aPlotter, LAYER_NUM aLayer, void PlotStandardLayer( BOARD* aBoard, PLOTTER* aPlotter, LAYER_MSK aLayerMask, const PCB_PLOT_PARAMS& aPlotOpt ); +/** + * Function PlotLayerOutlines + * plot copper outline of a copper layer. + * @param aBoard = the board to plot + * @param aPlotter = the plotter to use + * @param aLayerMask = the mask to define the layers to plot + * @param aPlotOpt = the plot options. Has meaning for some formats only + */ +void PlotLayerOutlines( BOARD *aBoard, PLOTTER* aPlotter, + LAYER_MSK aLayerMask, const PCB_PLOT_PARAMS& aPlotOpt ); + /** * Function PlotSilkScreen * plot silkscreen layers which have specific requirements, mainly for pads. diff --git a/pcbnew/plot_board_layers.cpp b/pcbnew/plot_board_layers.cpp index f299787716..7bfa48cb36 100644 --- a/pcbnew/plot_board_layers.cpp +++ b/pcbnew/plot_board_layers.cpp @@ -176,10 +176,18 @@ void PlotOneBoardLayer( BOARD *aBoard, PLOTTER* aPlotter, LAYER_NUM aLayer, case LAYER_N_15: case LAST_COPPER_LAYER: // Skip NPTH pads on copper layers ( only if hole size == pad size ): - plotOpt.SetSkipPlotNPTH_Pads( true ); // Drill mark will be plotted, // if drill mark is SMALL_DRILL_SHAPE or FULL_DRILL_SHAPE - PlotStandardLayer( aBoard, aPlotter, layer_mask, plotOpt ); + if( plotOpt.GetFormat() == PLOT_FORMAT_DXF ) + { + plotOpt.SetSkipPlotNPTH_Pads( false ); + PlotLayerOutlines( aBoard, aPlotter, layer_mask, plotOpt ); + } + else + { + plotOpt.SetSkipPlotNPTH_Pads( true ); + PlotStandardLayer( aBoard, aPlotter, layer_mask, plotOpt ); + } break; case SOLDERMASK_N_BACK: @@ -190,7 +198,12 @@ void PlotOneBoardLayer( BOARD *aBoard, PLOTTER* aPlotter, LAYER_NUM aLayer, // Plot solder mask: if( soldermask_min_thickness == 0 ) - PlotStandardLayer( aBoard, aPlotter, layer_mask, plotOpt ); + { + if( plotOpt.GetFormat() == PLOT_FORMAT_DXF ) + PlotLayerOutlines( aBoard, aPlotter, layer_mask, plotOpt ); + else + PlotStandardLayer( aBoard, aPlotter, layer_mask, plotOpt ); + } else PlotSolderMaskLayer( aBoard, aPlotter, layer_mask, plotOpt, soldermask_min_thickness ); @@ -202,12 +215,19 @@ void PlotOneBoardLayer( BOARD *aBoard, PLOTTER* aPlotter, LAYER_NUM aLayer, plotOpt.SetSkipPlotNPTH_Pads( false ); // Disable plot pad holes plotOpt.SetDrillMarksType( PCB_PLOT_PARAMS::NO_DRILL_SHAPE ); - PlotStandardLayer( aBoard, aPlotter, layer_mask, plotOpt ); + + if( plotOpt.GetFormat() == PLOT_FORMAT_DXF ) + PlotLayerOutlines( aBoard, aPlotter, layer_mask, plotOpt ); + else + PlotStandardLayer( aBoard, aPlotter, layer_mask, plotOpt ); break; case SILKSCREEN_N_FRONT: case SILKSCREEN_N_BACK: - PlotSilkScreen( aBoard, aPlotter, layer_mask, plotOpt ); + if( plotOpt.GetFormat() == PLOT_FORMAT_DXF ) + PlotLayerOutlines( aBoard, aPlotter, layer_mask, plotOpt ); + else + PlotSilkScreen( aBoard, aPlotter, layer_mask, plotOpt ); // Gerber: Subtract soldermask from silkscreen if enabled if( aPlotter->GetPlotterType() == PLOT_FORMAT_GERBER @@ -444,6 +464,115 @@ void PlotStandardLayer( BOARD *aBoard, PLOTTER* aPlotter, itemplotter.PlotDrillMarks(); } +/* Plot outlines of copper, for copper layer + */ +#include "clipper.hpp" +void PlotLayerOutlines( BOARD *aBoard, PLOTTER* aPlotter, + LAYER_MSK aLayerMask, const PCB_PLOT_PARAMS& aPlotOpt ) +{ + + BRDITEMS_PLOTTER itemplotter( aPlotter, aBoard, aPlotOpt ); + itemplotter.SetLayerMask( aLayerMask ); + + CPOLYGONS_LIST outlines; + + for( LAYER_NUM layer = FIRST_LAYER; layer < NB_PCB_LAYERS; layer++ ) + { + LAYER_MSK layer_mask = GetLayerMask( layer ); + + if( (aLayerMask & layer_mask ) == 0 ) + continue; + + outlines.RemoveAllContours(); + aBoard->ConvertBrdLayerToPolygonalContours( layer, outlines ); + + // Merge all overlapping polygons. + KI_POLYGON_SET kpolygons; + KI_POLYGON_SET ktmp; + outlines.ExportTo( ktmp ); + + kpolygons += ktmp; + + // Plot outlines + std::vector< wxPoint > cornerList; + + for( unsigned ii = 0; ii < kpolygons.size(); ii++ ) + { + KI_POLYGON polygon = kpolygons[ii]; + + // polygon contains only one polygon, but it can have holes linked by + // overlapping segments. + // To plot clean outlines, we have to break this polygon into more polygons with + // no overlapping segments, using Clipper, because boost::polygon + // does not allow that + ClipperLib::Path raw_polygon; + ClipperLib::Paths normalized_polygons; + + for( unsigned ic = 0; ic < polygon.size(); ic++ ) + { + KI_POLY_POINT corner = *(polygon.begin() + ic); + raw_polygon.push_back( ClipperLib::IntPoint( corner.x(), corner.y() ) ); + } + + ClipperLib::SimplifyPolygon( raw_polygon, normalized_polygons ); + + // Now we have one or more basic polygons: plot each polygon + for( unsigned ii = 0; ii < normalized_polygons.size(); ii++ ) + { + ClipperLib::Path& polygon = normalized_polygons[ii]; + cornerList.clear(); + + for( unsigned jj = 0; jj < polygon.size(); jj++ ) + cornerList.push_back( wxPoint( polygon[jj].X , polygon[jj].Y ) ); + + // Ensure the polygon is closed + if( cornerList[0] != cornerList[cornerList.size()-1] ) + cornerList.push_back( cornerList[0] ); + + aPlotter->PlotPoly( cornerList, NO_FILL ); + } + } + + // Plot pad holes + if( aPlotOpt.GetDrillMarksType() != PCB_PLOT_PARAMS::NO_DRILL_SHAPE ) + { + for( MODULE* module = aBoard->m_Modules; module; module = module->Next() ) + { + for( D_PAD* pad = module->Pads(); pad; pad = pad->Next() ) + { + wxSize hole = pad->GetDrillSize(); + + if( hole.x == 0 || hole.y == 0 ) + continue; + + if( hole.x == hole.y ) + aPlotter->Circle( pad->GetPosition(), hole.x, NO_FILL ); + else + { + wxPoint drl_start, drl_end; + int width; + pad->GetOblongDrillGeometry( drl_start, drl_end, width ); + aPlotter->ThickSegment( pad->GetPosition() + drl_start, + pad->GetPosition() + drl_end, width, SKETCH ); + } + } + } + } + + // Plot vias holes + for( TRACK* track = aBoard->m_Track; track; track = track->Next() ) + { + const VIA* via = dynamic_cast( track ); + + if( via && via->IsOnLayer( layer ) ) // via holes can be not through holes + { + aPlotter->Circle( via->GetPosition(), via->GetDrillValue(), NO_FILL ); + } + } + } +} + + /* Plot a solder mask layer. * Solder mask layers have a minimum thickness value and cannot be drawn like standard layers, * unless the minimum thickness is 0.